Uh oh!
There was an error while loading. Please reload this page.
fix(cli): render verbose log content literally instead of as Rich markup - #403
fix(cli): render verbose log content literally instead of as Rich markup#403Jason Robert (jrob5756) wants to merge 1 commit into
Conversation
`verbose_log_section` passed its content straight to `Panel`, and
`verbose_log` interpolated its message into `f"[{style}]{message}[/{style}]"`.
Rich parses a bare `str` as console markup, so any square-bracket tag in that
text was interpreted rather than displayed.
The content is untrusted. Rendered prompts carry agent output, plan text and
tool arguments verbatim, so an agent that merely writes `[/bold]` in a code
span produces an unbalanced closing tag and Rich raises MarkupError — which
takes down the whole workflow, not just the log line.
This is not hypothetical: a review agent filed a finding about unescaped
markup crashing a Textual screen, quoting `[/bold]` as the trigger. Rendering
the prompt that carried that finding to the next agent killed the run at
iteration 45, with the error the finding described.
The file-console path makes it worse than a verbosity-only bug: file logging
is enabled for every `--web-bg` run regardless of console verbosity, so a
background run is exposed even with `--quiet`.
Wrap both in `rich.text.Text`, which renders literally. The console keeps its
style, since `Text` accepts one directly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>Jason Robert (jrob5756)
commented
Aug 11, 2026
Closing this as superseded by #387, which landed in a58c88f while this branch was open. #387 fixes the same defect in both functions using the same
Worth recording why this was not obvious: this branch was cut before #387 merged, so the PR diff GitHub shows (and One finding from reviewing this does survive on |
Jason Robert (jrob5756)
commented
Aug 11, 2026
The residual |
Fixes#402.
The bug
verbose_log_sectionpassed its content straight toPanel, andverbose_loginterpolated its message intof"[{style}]{message}[/{style}]". Rich parses a plainstras console markup, so any square-bracket tag in that text was interpreted rather than displayed.That content is untrusted —
verbose_log_sectionis called with the rendered prompt (executor/agent.py:435), which carries agent output, plan text and tool arguments verbatim. An agent that writes[/bold]in a code span produces an unbalanced closing tag, Rich raisesMarkupError, and the exception propagates out of the log call and kills the workflow.It killed a real run at iteration 45, after seven epics had been committed. The trigger is worth reading twice: a review agent had filed a finding about unescaped markup crashing a Textual screen, quoting
[/bold]as the example. Rendering the prompt that carried that finding to the next agent raised the exact error the finding described.The file-console path makes this more than a verbosity-flag bug — file logging is on for every
--web-bgrun regardless of console verbosity, so background runs are exposed even with--quiet.The fix
Wrap both in
rich.text.Text, which renders literally.Texttakes a style directly, so the console output keeps its formatting and the file output is unchanged apart from no longer being parsed.Tests
Three regression tests in
tests/test_cli/test_logging.py, each asserting the literal tag survives to the output:test_verbose_log_section_rejects_markup_interpretation— console pathtest_verbose_log_section_file_output_rejects_markup— file path, reached with console verbosity off (the--web-bgcase)test_verbose_log_rejects_markup_interpretation—verbose_logVerified they fail on
mainwithMarkupErrorand pass with the fix.Verification
make check— ruff check, ruff format, ty: all passuv run pytest tests/test_cli/— 529 passed, 3 skippedNote on scope
I fixed
verbose_logalongsideverbose_log_sectioneven though only the latter caused the crash: it is the same one-line flaw in the adjacent function in the same file, and its callers interpolate plugin warnings and MCP server names that conductor does not control either. Happy to split it out if you would rather keep the change to the reproduced path.rich.markup.escapeis already used correctly incli/doctor.pyandgates/interrupt.py, so these two call sites look like they simply predate that pattern. #402 suggests a broader audit as follow-up.