Repository navigation
Conversation
The command output parser only ended an OSC, DCS, APC, PM or SOS string at BEL, ESC \ or a newline. A program that starts a title OSC and then prints a CSI without terminating it, such as ESC ]0;title ESC ESC [31m, had the rest of the line swallowed into the string, so the chat's command output lost it. Like xterm.js and the server's terminal history since pingdotgg#29604, an ESC that does not start ST now ends the string, and a second ESC restarts the sequence instead of being consumed as a two-byte escape. Bug: command output loses text after an unterminated OSC
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — Command output now preserves text following an unterminated terminal string sequence instead of swallowing it. The change is confined to shared output parsing, with existing output bounds and restricted escape handling retained. You can add or adjust custom eligibility rules. Learn more. |
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
|
Note Written by Verified in a real dev environmentverified unchanged head created a project pointing at a real git checkout through the web app, selected codex on printf 'broken:ok\033]0;title\033\033[31mred\033[0m\nplain:after\nvalid-bel:\033]0;hidden-title\007\033[32mgreen\033[0m\nvalid-st:\033]0;hidden-title\033\\\033[34mblue\033[0m\nlink:\033]8;;https://example.com\033\\example\033]8;;\033\\\n'the screenshots compare the exact same persisted provider output under each build. fresh codex turns on the pr also preserve the text and colors, including a command that splits the unterminated title and following CSI with a 10-second delay. collapsing/reopening the command output and reloading the client preserve the corrected final output. private title payloads stay hidden. blacksmith verification passed: the existing shared parser suite (18 tests), then Not verifiedincremental rendering before command completion: delayed real commands completed and their final output was checked, but the capture did not establish intermediate client frames. native ios/android, the electron shell, other providers, and remote/relay/tunnel connections were not exercised on this linux vm. some codex turns displayed provider recovery notices and then completed; this is not a claim that provider retries were fixed. |



Problem
The command output an agent's tool call streams into a thread loses text after an unterminated OSC. A program that sets a title with
ESC ]0;titleand then printsESC [31mbefore it sends BEL orESC \has the rest of the line swallowed into the title string, soprintf '\033]0;title\033\033[31mred\033[0m\n'shows an empty line instead ofred.Change
escapeLengthinpackages/shared/src/terminalOutput.tsended an OSC, DCS, APC, PM or SOS string only at BEL,ESC \or a newline, and skipped over any other ESC. Like xterm.js, an ESC that does not start ST now ends the string, and the new sequence starts at that ESC. A second ESC right after an ESC now restarts the sequence, so it is no longer read as a two-byte escape that eats the ESC of the CSI after it. A string that is still open at the end of a chunk is held as before, so a payload split across chunks stays hidden.The parser is shared. The fold runs in the server's
CommandOutputHub(normalised and resume text) and inpackages/client-runtime/src/state/commandOutput.ts(appendTerminalOutput, which web and mobile use); web'sCommandOutputPaneland mobile's command output then render its spans. The integrated terminal (PTY) does not use this parser and is unchanged.Valid sequences render as before: ST in one chunk or split across chunks, BEL-terminated OSC, OSC 8 hyperlinks closed by ST or BEL, a sixel DCS, and tmux passthrough of an OSC 52.
Scope and approval
This is the command-output twin of #29604 ("terminal history keeps output after an unterminated OSC"), which made the server's terminal history sanitizer end a string at a non-ST ESC and restart a sequence at a second ESC. The shared command-output parser was missing both rules for strings and loses text the same way. One case of #29604's restart rule is left out: after a charset designation's intermediate byte (
ESC ( ESC), this parser still reads a three-byte escape, as it did before. It is a small, focused fix for an obvious bug: two lines in one function and one test assertion, with no contract, setting or other behaviour change.Open #28924, #29884, #27496, #21542 and #24042 also edit
terminalOutput.ts, and none touchesescapeLength. #28924 rewrites the feed loop; a one-character escape still works with itslastIndex = printablereset.Verification
I added a case to the existing
drops an escape sequence that never terminates and resumes after ittest inpackages/shared/src/terminalOutput.test.ts. It feedsok ESC ]0;title ESCand thenESC [31mredas two chunks and expectsokfollowed by the redred.On
main(vp test run packages/shared/src/terminalOutput.test.ts):With the fix:
vp run check:changed: format, lint, knip, typecheck (34 packages) and tests (3 files) all pass.In a running dev build (web, Windows): a Claude Haiku 5.5 turn ran
printf 'ok�]0;title��[31mred�[0m 'through its Bash tool, and the command's output was opened in the thread. The same stored output was viewed once withmain'sterminalOutput.tsand once with this branch's, reloading the page in between.main): onlyokokthenredin redThe "T3 Code restarted and resumed this turn" row comes from the dev server restarting while the turn ran, not from this change. The captures are the preview panel's visible area (1168 by 730). Not checked: the mobile command output, which uses the same parser.
Bug: command output loses text after an unterminated OSC
This was written with Claude Opus 5.5 in Claude Code, driven from T3 Code.