Skip to content

fix(#422): escape Rich markup in all console content - #438

Open
LHMQ878 wants to merge 1 commit into
bytedance:mainfrom
LHMQ878:fix/rich-markup-escape-console
Open

fix(#422): escape Rich markup in all console content#438
LHMQ878 wants to merge 1 commit into
bytedance:mainfrom
LHMQ878:fix/rich-markup-escape-console

Conversation

@LHMQ878

@LHMQ878 LHMQ878 commented Jul 27, 2026

Copy link
Copy Markdown

Description

trae-cli crashes with rich.errors.MarkupError whenever text it displays happens to contain something Rich reads as a closing markup tag — e.g. [/'A', 1, /'B'] in a tool result, or [/\-\+.$%*@?|] in model output. The crash masks the original error instead of reporting it.

This is the remaining half of #281. #341 fixed part of it, but its own fix is undone by an f-string, and the main rendering path was never covered at all.

More Information

1. Text interpolated back into a str (the traceback in #422)

#341 wrapped error strings in rich.text.Text, which is markup-safe, but then printed three of them like this:

error_text = Text(f"Unexpected error: {e}", style="red")
console.print(f"\n{error_text}")   # ← f-string converts Text back to str

Interpolating a Text into an f-string calls __str__ and yields a plain str, so Rich parses it as markup again and the escaping the Text existed to provide is lost. That is exactly the line the reported traceback ends on (cli.py:408). The two sites in the same handler that print the Text directly never crashed — which is why the bug looked partially fixed.

Fixed by emitting the newline separately and printing the Text itself. traceback.format_exc() is now wrapped in Text too: it embeds the same exception message, so console.print(traceback.format_exc()) on the very next line crashes identically — that print is on the path the issue expects to show "the original exception and traceback".

2. rich.markup.escape was never used anywhere in the repo

grep -rn escape trae_agent/ returns no call sites. Meanwhile generate_agent_step_table (utils/cli/cli_console.py) interpolates raw, arbitrary text into table cells:

row source of the text
LLM Response model output
Tools tool names
tool call table tool arguments and tool results
Reflection model output
Error exception messages

Any of these containing [/...] raises MarkupError when the table is rendered. This matches the reporter's note that "the agent explored parser/grammar code and tool output contained strings like the examples above" — grammar/parser source is full of bracketed tokens.

print_task_details in both simple_console.py and rich_console.py has the same problem with value, which includes the user's own task description, so trae-cli run "fix the [/foo] parser" crashes before the agent even starts.

Each of those values is now passed through rich.markup.escape. The style tags the table emits itself ([{color}]…[/{color}], [cyan]…[/cyan], [bold]…[/bold]) stay unescaped, so styling is unchanged — one of the tests pins that.

Because generate_agent_step_table is shared by the simple and rich consoles, one fix covers both --console-type values.

Validation

New test file tests/utils/test_cli_console_markup.py — 6 cases rendering an AgentStep through the real Console with the two markup-like strings from the bug report placed in LLM response / tool arguments / tool result / reflection / error, plus a case asserting the status row is still styled (i.e. the fix escapes content without escaping the table's own markup).

$ python -m pytest tests/utils/test_cli_console_markup.py -q
......                                                          [100%]

Control experiment — git stash the source change and re-run: all 6 fail, with

rich.errors.MarkupError: closing tag '[/'A', 1, /'B']' at position 10 doesn't match any open tag

which is the same error class and message as the report.

No regressions: comparing the full pytest tests/ failure set before and after this change, the only difference is those 6 tests flipping from fail to pass. The 15 other failures are identical on both sides and pre-existing in my environment (Google / OpenRouter / Ollama tests need live API keys or a running Ollama; two bash / json_edit tests are Windows-specific).

ruff check and ruff format --check pass on all 5 files. mypy reports only the pre-existing docker.errors missing-stubs error, confirmed identical with the change stashed.

Verified against both rich 13.7.1 and 15.0.0.

Linked Issues

Resolves #422
Related to #281

bytedance#341 wrapped error strings in `rich.text.Text` but then printed them via
`console.print(f"\n{error_text}")`. Interpolating a `Text` into an f-string
converts it back to `str`, so Rich re-parses it as markup and the escaping
is undone — which is why the traceback in bytedance#422 still points at that line.
Print the `Text` directly, and wrap `traceback.format_exc()` too, since a
traceback carrying the same text crashes identically.

The larger gap is that `rich.markup.escape` was never used anywhere.
`generate_agent_step_table` interpolates model output, tool arguments, tool
results, reflections and errors straight into table cells, and both
`print_task_details` implementations interpolate the user's task
description into markup. Any `[...]` shaped like a closing tag raises
MarkupError there, which matches the report's note that the agent had been
exploring parser/grammar code. Escape each of those values; the surrounding
style tags stay markup so styling is unaffected.

Covers both console types, since `generate_agent_step_table` is shared by
the simple and rich consoles.
@CLAassistant

CLAassistant commented Jul 27, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: CLI crashes with Rich MarkupError when error text contains bracket-like output

2 participants