#15054 · @alberti42 · opened Feb 25, 2026 at 11:58 AM UTC · last updated Mar 21, 2026 at 8:59 PM UTC

fix(opencode): instruct all chat agents to use KaTeX-compatible math syntax

appfix
82
+2967 files

Score breakdown

Impact

9.0

Clarity

9.0

Urgency

8.0

Ease Of Review

10.0

Guidelines

9.0

Readiness

9.0

Size

9.0

Trust

6.0

Traction

6.0

Summary

This PR fixes a critical bug where LLM-generated math equations were unreadable due to incorrect LaTeX syntax for KaTeX rendering. It achieves this by injecting explicit math formatting rules into agent system prompts. The solution is clearly explained and visually verified with before/after screenshots.

Open in GitHub

Description

Issue for this PR

Closes #15053

Type of change

  • [x] Bug fix
  • [ ] New feature
  • [ ] Refactor / code improvement
  • [ ] Documentation

What does this PR do?

The opencode frontend renders math with KaTeX, which requires $...$ for inline equations and $$...$$ (flush-left, surrounded by blank lines) for display equations. Without explicit instructions, LLMs default to \(...\) and \[...\] or wrap display equations in fenced code blocks — none of which KaTeX can render. The result is raw LaTeX source appearing in the chat instead of rendered formulas.

This PR adds a shared math_formatting_rules.txt file and injects it into every primary-agent system prompt via a {MATH_FORMATTING_RULES} placeholder. The placeholder is inserted inside the existing formatting/style section of each prompt rather than appended at the end. This matters: LLMs follow instructions better when related rules are grouped together. Appending at the end (e.g. via AGENTS.md) would scatter formatting guidance across the prompt, with general style rules at the top and math rules at the bottom. Placing them together gives the agent a coherent picture of all formatting expectations in one place.

beast.txt (used for sub-agents that never produce chat output) is excluded intentionally. The token overhead is negligible (~13 lines).

One implementation note worth calling out: the injection uses prompt.replace("{MATH_FORMATTING_RULES}", () => rules) with a function rather than a plain string. JavaScript's replace() treats $ specially in replacement strings (e.g. $` inserts the portion before the match), so a plain string would silently corrupt the LaTeX delimiters. Passing a function bypasses this entirely.

How did you verify your code works?

Tested manually with OpenAI (GPT-4o / GPT-5), Claude (claude-sonnet), and Gemini (gemini-2.5-pro) by asking each agent to write a response containing both inline and display equations. All three correctly produced KaTeX-renderable output after the fix.

Screenshots / recordings

Before — raw LaTeX source dumped as plain text, unreadable:

before-this-fix

After — equations rendered by KaTeX, properly formatted:

after-this-fix

Checklist

  • [x] I have tested my changes locally
  • [x] I have not included unrelated changes in this PR

Linked Issues

#15053 bug: math equations rendered as raw LaTeX source — agents not instructed to use KaTeX-compatible syntax

View issue

Comments

PR comments

alberti42

@adamdotdevin @Hona - gentle ping. I would appreciate feedback when you have a moment. I am glad to invest more time, add more tests for other providers, and polish it along your indications.

Changed Files

packages/opencode/src/session/prompt/anthropic.txt

+10
@@ -14,6 +14,7 @@ When the user directly asks about OpenCode (eg. "can OpenCode do...", "does Open
# Tone and style
- Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.
- Your output will be displayed on a command line interface. Your responses should be short and concise. You can use GitHub-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.
- {MATH_FORMATTING_RULES}
- Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.
- NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files.

packages/opencode/src/session/prompt/codex_header.txt

+10
@@ -65,6 +65,7 @@ You are producing plain text that will later be styled by the CLI. Follow these
- Bullets: use - ; merge related points; keep to one line when possible; 4–6 per list ordered by importance; keep phrasing consistent.
- Monospace: backticks for commands/paths/env vars/code ids and inline examples; use for literal keyword bullets; never combine with **.
- Code samples or multi-line snippets should be wrapped in fenced code blocks; include an info string as often as possible.
- {MATH_FORMATTING_RULES}
- Structure: group related bullets; order sections general → specific → supporting; for subsections, start with a bolded keyword bullet, then items; match complexity to the task.
- Tone: collaborative, concise, factual; present tense, active voice; self‑contained; no "above/below"; parallel wording.
- Don'ts: no nested bullets/hierarchies; no ANSI codes; don't cram unrelated keywords; keep keyword lists short—wrap/reformat if long; avoid naming formatting styles in answers.

packages/opencode/src/session/prompt/gemini.txt

+10
@@ -42,6 +42,7 @@ When requested to perform tasks like fixing bugs, adding features, refactoring,
- **Clarity over Brevity (When Needed):** While conciseness is key, prioritize clarity for essential explanations or when seeking necessary clarification if a request is ambiguous.
- **No Chitchat:** Avoid conversational filler, preambles ("Okay, I will now..."), or postambles ("I have finished the changes..."). Get straight to the action or answer.
- **Formatting:** Use GitHub-flavored Markdown. Responses will be rendered in monospace.
- {MATH_FORMATTING_RULES}
- **Tools vs. Text:** Use tools for actions, text output *only* for communication. Do not add explanatory comments within tool calls or code blocks unless specifically part of the required code/command itself.
- **Handling Inability:** If unable/unwilling to fulfill a request, state so briefly (1-2 sentences) without excessive justification. Offer alternatives if appropriate.

packages/opencode/src/session/prompt/math_formatting_rules.txt

+120
@@ -0,0 +1,12 @@
Math formatting:
* Inline LaTeX equations: use `$...$` (never `\( ... \)`)
* Prefer display equations for complex formulas or multi-term expressions.
* Display equations: use `$$ ... $$` (never `\[ ... \]`), with a blank line before the opening `$$` and after the closing `$$`.
* Display equations: the `$$` delimiter lines must be flush-left (no indentation), or some renderers treat it as code.
* Example (with required blank lines):
$$
E = mc^2
$$
* Do not wrap display equations in code fences (this prevents rendering in the browser).

packages/opencode/src/session/prompt/qwen.txt

+10
@@ -13,6 +13,7 @@ When the user directly asks about opencode (eg 'can opencode do...', 'does openc
# Tone and style
You should be concise, direct, and to the point. When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system).
Remember that your output will be displayed on a command line interface. Your responses can use GitHub-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.
{MATH_FORMATTING_RULES}
Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.
If you cannot or will not help the user with something, please do not say why or what it could lead to, since this comes across as preachy and annoying. Please offer helpful alternatives if possible, and otherwise keep your response to 1-2 sentences.
Only use emojis if the use

packages/opencode/src/session/prompt/trinity.txt

+10
@@ -3,6 +3,7 @@ You are opencode, an interactive CLI tool that helps users with software enginee
# Tone and style
You should be concise, direct, and to the point. When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system).
Remember that your output will be displayed on a command line interface. Your responses can use GitHub-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.
{MATH_FORMATTING_RULES}
Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.
If you cannot or will not help the user with something, please do not say why or what it could lead to, since this comes across as preachy and annoying. Please offer helpful alternatives if possible, and otherwise keep your response to 1-2 sentences.
Only use emojis if the user

packages/opencode/src/session/system.ts

+126
@@ -9,24 +9,30 @@ import PROMPT_GEMINI from "./prompt/gemini.txt"
import PROMPT_CODEX from "./prompt/codex_header.txt"
import PROMPT_TRINITY from "./prompt/trinity.txt"
import PROMPT_MATH_FORMATTING_RULES from "./prompt/math_formatting_rules.txt"
import type { Provider } from "@/provider/provider"
import type { Agent } from "@/agent/agent"
import { PermissionNext } from "@/permission/next"
import { Skill } from "@/skill"
function inject(prompt: string): string {
const rules = PROMPT_MATH_FORMATTING_RULES.trim()
return prompt.replace("{MATH_FORMATTING_RULES}", () => rules)
}
export namespace SystemPrompt {
export function instructions() {
return PROMPT_CODEX.trim()
return inject(PROMPT_CODEX).trim()
}
export function provider(model: Provider.Model) {
if (model.api.id.includes("gpt-5")) return [PROMPT_CODEX]
if (model.api.id.includes("gpt-5")) return [inject(PROMPT_CODEX)]
if (model.api.id.includes("gpt-") || model.api.id.includes("o1") || model.api.id.includes("o3"))
return [PROMPT_BEAST]
if (model.api.id.includes("gemini-")) return [PROMPT_GEMINI]
if (model.api.id.includes("claude")) return [PROMPT_ANTHROPIC