Skip to content

fix(shared): command output keeps text after an unterminated OSC - #30479

Open
spoukyii wants to merge 2 commits into
pingdotgg:mainfrom
spoukyii:users/spoukyii/command-output-unterminated-osc
Open

spoukyii wants to merge 2 commits into
pingdotgg:mainfrom
spoukyii:users/spoukyii/command-output-unterminated-osc

Conversation

@spoukyii

@spoukyii spoukyii commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

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;title and then prints ESC [31m before it sends BEL or ESC \ has the rest of the line swallowed into the title string, so printf '\033]0;title\033\033[31mred\033[0m\n' shows an empty line instead of red.

Change

escapeLength in packages/shared/src/terminalOutput.ts ended 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 in packages/client-runtime/src/state/commandOutput.ts (appendTerminalOutput, which web and mobile use); web's CommandOutputPanel and 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 touches escapeLength. #28924 rewrites the feed loop; a one-character escape still works with its lastIndex = printable reset.

Verification

I added a case to the existing drops an escape sequence that never terminates and resumes after it test in packages/shared/src/terminalOutput.test.ts. It feeds ok ESC ]0;title ESC and then ESC [31mred as two chunks and expects ok followed by the red red.

On main (vp test run packages/shared/src/terminalOutput.test.ts):

× drops an escape sequence that never terminates and resumes after it
AssertionError: expected 'ok' to be 'ok\u001b[0;31mred\u001b[0m' // Object.is equality
Tests  1 failed | 17 passed (18)

With the fix:

Test Files  1 passed (1)
     Tests  18 passed (18)

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 with main's terminalOutput.ts and once with this branch's, reloading the page in between.

Before (main): only ok After (this branch): ok then red in red
Before: the command output shows only ok After: the command output shows ok followed by red in red

The "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.

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
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XS 0-9 changed lines (additions + deletions). labels Nov 4, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 7b493d4

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.

@coderabbitai

coderabbitai Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Walkthrough

The terminal output parser now ends an escape sequence when a new ESC interrupts it. A test checks that subsequent red text is retained with normalized SGR codes.

Changes: Terminal escape sequence recovery

Layer / File(s) Summary
Restart interrupted escape sequences
packages/shared/src/terminalOutput.ts, packages/shared/src/terminalOutput.test.ts
escapeLength ends an existing sequence at a new ESC, allowing it to begin another sequence. String sequences still remain incomplete when the input ends with ESC. The test checks that the interrupted title sequence is discarded and subsequent red text is retained with normalized SGR codes.

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix

Suggested reviewers: romil-bijarnia, hieu-lee

Merge Risk: Minimal · up to 7b493

The parser change is narrowly scoped, and the inspected server and client output paths preserve the intended recovery behavior. No merge-blocking risk is identified.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the primary fix: preserving command output after an unterminated OSC sequence.
Description check Passed The description includes complete Problem, Change, Scope and approval, and Verification sections. It explains the failure, implementation, affected components, related issue, focused test results, bro…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description includes complete Problem, Change, Scope and approval, and Verification sections. It explains the failure, implementation, affected components, related issue, focused test results, broader checks, manual verification, and the unverified mobile case.

Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between ad4bb6d and 7b493d4.

Files selected for processing: 2
  • packages/shared/src/terminalOutput.test.ts
  • packages/shared/src/terminalOutput.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.

@maria-rcks

maria-rcks commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified unchanged head 7b493d4ed789192d9c80ba582175775b5cabc7d5 on linux with repository playwright and /usr/bin/google-chrome.

created a project pointing at a real git checkout through the web app, selected codex gpt-6.1-sol, and submitted real provider turns. no mocks, fixtures, or replay provider. ran main and the pr sequentially with isolated app-created state.

on github/main (d51bc58f5e05123a416a3737ac4bce42d1d76b7c), this codex command's expanded output dropped red:

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'
main: broken:ok
pr:   broken:okred  (red is red)
both: plain:after
      valid-bel:green  (green is green)
      valid-st:blue    (blue is blue)
      link:example

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.

main drops red after the interrupted title

pr preserves red and the neighboring valid output

collapsing and reopening real codex command output preserves red and valid colors

blacksmith verification passed: the existing shared parser suite (18 tests), then vp run check:changed ad4bb6d6fa0af9072fef92b540c12f04997036d5 with format, lint, knip, typechecks for 34 packages, and 19 tests across two files. the explicit base was needed because the worker has snapshot history. github checks are green. no source changes were needed.

Not verified

incremental 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.

This branch has not been deployed

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

Labels

size:XS 0-9 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants