Repository navigation
fix(shared): read MCP event streams whatever the case of the content type - #35815
Conversation
…ad as events Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…type Media types are case-insensitive (RFC 9110), but responsePayloads matched "text/event-stream" case-sensitively, so a header like Text/Event-Stream sent the SSE body to JSON.parse and failed the MCP tool call. Lowercase the header before matching. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lowercasing the whole Content-Type and using includes let a parameter value such as profile=".../Text/Event-Stream" on a JSON response select the SSE reader and drop the JSON envelope. Compare only the trimmed media type before the first semicolon, case-insensitively. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — MCP responses now select the event-stream decoder regardless of media-type casing, without matching text inside Content-Type parameters. The change is limited to decoder selection and leaves parsing and error handling unchanged. You can add or adjust custom eligibility rules. Learn more. |
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Other scenariosAll 64 scenario measurements match All 64 scenario measurements
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
**v0.1.10-nightly.20261113.4630** · 123 changes from 15 contributors - **Pull request images.** Preview images in the pull request Code tab, including images without text patches. - **Faster long threads.** Send, edit, and steer queued messages faster in long-running threads. - **Watched pull requests.** Threads finish working when watched pull request checks complete. - **Commit messages.** A failed commit keeps your typed message so you can retry. - **Checkpoint restores.** New turns wait for overlapping checkpoint restores, preventing restores from overwriting their work. - **Worktree cleanup.** Worktrees remain available after turns that finish without a commit. - **Forgejo merges.** Merge pull requests in Forgejo repositories that allow only fast-forward merges. ### Plus 99 more bug fixes and performance improvements. <details> <summary>Full changelog</summary> ## What's Changed * fix(server): remote status keeps a local upstream named without a slash by @juliusmarminge in pingdotgg/t3code#35766 * fix(server): a watched PR stops holding its thread once its checks finish by @juliusmarminge in pingdotgg/t3code#33214 * fix(server): a slow database write no longer freezes the server by @tnfssc in pingdotgg/t3code#35728 * fix(mobile): Android sheets scroll without per-feature platform checks by @juliusmarminge in pingdotgg/t3code#34207 * docs: drop the GitCafe mirror runbook for a generic forge mirror by @juliusmarminge in pingdotgg/t3code#35773 * fix(ssh): timed-out SSH commands no longer leave a proxy helper running by @juliusmarminge in pingdotgg/t3code#35775 * fix(dev): long-running dev servers no longer leak memory when other worktrees install by @t3dotgg in pingdotgg/t3code#35740 * fix(server): Changes totals no longer take index.lock by @juliusmarminge in pingdotgg/t3code#35770 * fix(server): keep repository identity for roots whose name ends in a space by @juliusmarminge in pingdotgg/t3code#35767 * fix(server): restored worktrees find pushed thread work on a non-origin remote by @juliusmarminge in pingdotgg/t3code#35769 * fix(ssh): an alias no longer hides known_hosts entries on other ports by @juliusmarminge in pingdotgg/t3code#35779 * fix(server): IPv4-mapped loopback SSH device targets count as this machine by @juliusmarminge in pingdotgg/t3code#35774 * fix(ssh): host discovery follows Include globs in directory components by @juliusmarminge in pingdotgg/t3code#35771 * fix(server): Commit finishes a merge resolved to the current branch's tree by @juliusmarminge in pingdotgg/t3code#35778 * fix(server): gh default remote lookup ignores an inherited GIT_DIR by @juliusmarminge in pingdotgg/t3code#35764 * docs: FAQ says the Claude 5-hour limit drain is fixed by @linushohne in pingdotgg/t3code#31473 * fix(server): fork PR worktrees no longer fail when a local branch owns the t3code/pr-<n> prefix by @juliusmarminge in pingdotgg/t3code#35768 * fix(web): a failed commit keeps the commit message you typed by @juliusmarminge in pingdotgg/t3code#35781 * fix(server): branches whose names start with a parenthesis are no longer reported as detached by @juliusmarminge in pingdotgg/t3code#35780 * test(ssh): launcher harness no longer races its background nohup by @juliusmarminge in pingdotgg/t3code#35788 * fix(web): Resume replaces Stop without a lingering Stopped button by @juliusmarminge in pingdotgg/t3code#35755 * test(web): typecheck the project file refresh test by @maschwenk in pingdotgg/t3code#34061 * docs(codex): explain native image generation setup by @dpreil-ydlp in pingdotgg/t3code#31426 * perf(shared): command output failure scan rejects clean output in one pass by @hieu-lee in pingdotgg/t3code#26935 * test(web): sidebar drag test opens its folder past the shift guard by @juliusmarminge in pingdotgg/t3code#35803 * feat(mobile): secret field gets an eye toggle to show what you typed by @t3dotgg in pingdotgg/t3code#35783 * fix(mobile): a cold launch shows the cached thread rows with the first frame by @t3dotgg in pingdotgg/t3code#35683 * fix(server): ACP terminal MCP calls answer interleaved server requests by @juliusmarminge in pingdotgg/t3code#35805 * fix(web): "Try again" recovers from a failed page chunk load by @juliusmarminge in pingdotgg/t3code#35806 * fix(shared): read MCP event streams whatever the case of the content type by @juliusmarminge in pingdotgg/t3code#35815 * test(web): sidebar drag test ignores a fading row clone after folder collapse by @juliusmarminge in pingdotgg/t3code#35817 * fix(server): ACP streamed chunks compare message ids exactly by @juliusmarminge in pingdotgg/t3code#35836 * fix(install): name the archive when its download fails by @juliusmarminge in pingdotgg/t3code#35854 * test(server): drop the flaky Kiro provider_thread_resume replay by @juliusmarminge in pingdotgg/t3code#35867 * fix(web): arrow and page keys scroll the conversation again after a click or F6 by @juliusmarminge in pingdotgg/t3code#27209 * fix(kiro): compaction is refused when Kiro does not advertise _kiro/session/compact by @juliusmarminge in pingdotgg/t3code#35866 * fix(server): show self-resumed Claude subagents as running by @juliusmarminge in pingdotgg/t3code#23831 * fix(web): preserve single tildes in sent user messages by @juliusmarminge in pingdotgg/t3code#35859 * fix(desktop): clicking inside the in-app browser closes open host menus by @voida6 in pingdotgg/t3code#23141 * fix(desktop): update browser tab titles without navigation by @voltcrash in pingdotgg/t3code#29452 * fix(web,mobile): triage follows the draft when you switch machines by @pnupu in pingdotgg/t3code#33782 * fix(server): storage cleanup treats symlinked spellings of a worktree as one checkout by @juliusmarminge in pingdotgg/t3code#25735 * fix(server): migrate-dev-db refuses unimported legacy threads by @juliusmarminge in pingdotgg/t3code#23191 * fix(web): opening a thread on a touch tablet no longer focuses the composer by @juliusmarminge in pingdotgg/t3code#29496 * fix(acp): linked terminal output no longer reopens a completed tool call by @juliusmarminge in pingdotgg/t3code#35844 * fix(acp): root tool content replacements replace streamed output by @juliusmarminge in pingdotgg/t3code#35862 * fix(forgejo): fast-forward-only repositories can merge pull requests by @juliusmarminge in pingdotgg/t3code#32738 * fix(server): linking says why each address failed discovery by @juliusmarminge in pingdotgg/t3code#30884 * fix(terminal): show recovery guidance when a project folder is missing by @juliusmarminge in pingdotgg/t3code#35849 * fix(client-runtime): reopened files show host edits instead of the first read by @juliusmarminge in pingdotgg/t3code#29409 * fix(server): preview resize and status report the real size of desktop tabs by @Entrpi in pingdotgg/t3code#32854 * fix(server): migration 64 skips thread payloads that are not valid JSON by @juliusmarminge in pingdotgg/t3code#35884 * fix(web): thread navigation shortcuts work from the terminal and macOS composer by @juliusmarminge in pingdotgg/t3code#26433 * fix(desktop): an unreadable settings file is no longer overwritten by a bounds save by @juliusmarminge in pingdotgg/t3code#35890 * fix(codex): accept hook event names newer than the pinned protocol by @juliusmarminge in pingdotgg/t3code#35885 * fix(opencode): /goal clear with no goal set finishes as a no-op by @juliusmarminge in pingdotgg/t3code#35893 * fix(ssh): a slow reconnect no longer replaces a healthy port forward by @juliusmarminge in pingdotgg/t3code#35874 * fix(desktop): one preview viewer or recording stopping no longer freezes the others by @juliusmarminge in pingdotgg/t3code#31051 * fix(server): Codex question answers keep their native question id by @juliusmarminge in pingdotgg/t3code#35886 * test(server): windowed history pages are checked against the full index at the window edges by @t3dotgg in pingdotgg/t3code#35877 * fix(clients): answer history no longer shows provider context from review comments by @juliusmarminge in pingdotgg/t3code#35897 * fix(server): refresh sync status after agent merges by @Yash-Singh1 in pingdotgg/t3code#35919 * fix(web): copying an image with a spaced path keeps it an image by @juliusmarminge in pingdotgg/t3code#35914 * fix(server): Force stop no longer tears down a queued follow-up that started during cleanup by @juliusmarminge in pingdotgg/t3code#31038 * fix(azure-devops): TF400733 throttling fails diffs as rate-limited by @juliusmarminge in pingdotgg/t3code#35929 * fix(acp): v2 tool updates clear nulled fields and replace raw input by @juliusmarminge in pingdotgg/t3code#35869 * fix(server): interrupted SSH device bootstrap no longer leaks its forward by @juliusmarminge in pingdotgg/t3code#35928 * fix(mobile): Files search no longer carries into another workspace by @juliusmarminge in pingdotgg/t3code#35902 * fix(web): copying an alert callout keeps its alert Markdown by @juliusmarminge in pingdotgg/t3code#35932 * fix(server): Codex shadow home shares the instance environment CODEX_HOME by @juliusmarminge in pingdotgg/t3code#35912 * fix(preview): remote viewers recover from stalled first captures by @juliusmarminge in pingdotgg/t3code#25381 * fix(server): new turns wait for overlapping checkpoint file restores by @juliusmarminge in pingdotgg/t3code#23671 * fix(server): preview_navigate returns the committed page when load is slow by @juliusmarminge in pingdotgg/t3code#26127 * fix(server): unchanged-worktree cleanup waits for the thread to settle or archive by @juliusmarminge in pingdotgg/t3code#35941 * fix(web): zoom shortcuts work while a desktop browser page has focus by @juliusmarminge in pingdotgg/t3code#35947 * fix(server): ACP threads accept a model change after their session detaches by @juliusmarminge in pingdotgg/t3code#35951 * fix(server): service launcher keeps a T3 home that ends in a space by @juliusmarminge in pingdotgg/t3code#35958 * fix(server): launcher repoints the unit the systemd manager loads by @juliusmarminge in pingdotgg/t3code#35966 * fix(server): refuse a Linux service for a T3 home systemd cannot load by @juliusmarminge in pingdotgg/t3code#35968 * perf(server): sending, editing and steering queued messages no longer read a long thread's whole history by @juliusmarminge in pingdotgg/t3code#29521 * fix(server): Codex protocol records go to the log of the thread they belong to by @juliusmarminge in pingdotgg/t3code#35975 * fix(cli): service.env updates keep quoted multiline values intact by @juliusmarminge in pingdotgg/t3code#35976 * fix(web): open code blocks and diffs follow a changed word wrap default by @juliusmarminge in pingdotgg/t3code#35922 * fix(desktop): keep WSL distros whose names start or end with a dot or hyphen by @juliusmarminge in pingdotgg/t3code#35971 * test: run clock-free live tests on TestClock by @juliusmarminge in pingdotgg/t3code#35868 * fix(server): SSH device test rejects Node below agent-device's 22.12 floor by @juliusmarminge in pingdotgg/t3code#35991 * fix(server): Simlock renewals no longer exceed the gateway's lease TTL cap by @juliusmarminge in pingdotgg/t3code#35988 * test: drop Effect.scoped wrappers that @effect/vitest already applies by @juliusmarminge in pingdotgg/t3code#35993 * fix(device): Simlock viewer stops retrying an ended lease and keeps its reason by @juliusmarminge in pingdotgg/t3code#35994 * fix(server): Stop waits for the turn to settle instead of polling by @juliusmarminge in pingdotgg/t3code#35948 * ci: label issues from vouched and denounced authors, denounce coygeek by @juliusmarminge in pingdotgg/t3code#36021 * fix(scripts): dev runner ignores the parent T3 server's port and host by @juliusmarminge in pingdotgg/t3code#36019 * perf(server): reduce allocation while reading usage history by @StiensWout in pingdotgg/t3code#36009 * fix(web): terminal paste keeps a leading U+FEFF without bracketed paste by @juliusmarminge in pingdotgg/t3code#36018 * fix(server): BEL inside a DCS no longer ends it in terminal history by @juliusmarminge in pingdotgg/t3code#36020 * fix(server): terminal history drops the ESC Z identity query by @juliusmarminge in pingdotgg/t3code#36024 * test(server): blocked pointer-file scan test waits for the unblocked read by @juliusmarminge in pingdotgg/t3code#36031 * fix(web): terminal OSC 8 file URIs open the file they name by @juliusmarminge in pingdotgg/t3code#36025 * fix(server): terminal history strips queries with embedded controls by @juliusmarminge in pingdotgg/t3code#36027 * fix(mobile): pasted images over the size limit attach instead of vanishing by @juliusmarminge in pingdotgg/t3code#35845 * fix(mobile): Android terminal resizes the engine when only the cell size changes by @juliusmarminge in pingdotgg/t3code#36023 * fix(server): t3_thread_read results fit MCP clients' output limit by @juliusmarminge in pingdotgg/t3code#33455 * fix(desktop): AppImage t3 shim no longer grows the CLI compile cache on every call by @juliusmarminge in pingdotgg/t3code#33885 * ci: agent context budget runs are no longer cancelled by label bursts by @juliusmarminge in pingdotgg/t3code#36049 * fix(server): a decoded U+009C no longer ends an OSC string in terminal history by @juliusmarminge in pingdotgg/t3code#36022 * fix(server): speech worker no longer segfaults when the backend dies mid-dictation by @juliusmarminge in pingdotgg/t3code#33624 * fix(codex): image items identified by fileId are no longer dropped by @juliusmarminge in pingdotgg/t3code#35883 * perf(server): linked-thread counts update when links change instead of polling every 10s by @juliusmarminge in pingdotgg/t3code#35865 * fix(scripts): scenarios reach the screen and say why they could not run by @juliusmarminge in pingdotgg/t3code#36054 * docs: AGENTS.md and the FAQ list Pi, Muse Code, Kiro, and fx by @only21mil in pingdotgg/t3code#33906 * fix(web): PR Code tab previews images the host sends no patch for by @juliusmarminge in pingdotgg/t3code#36056 * fix(web): pull request comment tooltips follow the Time format setting by @juliusmarminge in pingdotgg/t3code#35396 * fix(server): repository identity honors the gh default remote by @juliusmarminge in pingdotgg/t3code#21574 * fix(server): a rejected non-fast-forward push explains itself under a translated Git by @juliusmarminge in pingdotgg/t3code#35386 * fix(web): provider rows select from anywhere in the row by @juliusmarminge in pingdotgg/t3code#32537 * fix(client-runtime): imported threads that never ran no longer sit in Working as Waiting by @juliusmarminge in pingdotgg/t3code#33042 * fix(web,mobile): MCP app calls keep their tool row and result beside the app card by @juliusmarminge in pingdotgg/t3code#33347 * fix(forgejo): tea API failures keep Gitea's reason by @juliusmarminge in pingdotgg/t3code#32244 * fix(server): reap recorded Linux Claude survivors before recovery by @juliusmarminge in pingdotgg/t3code#22834 * fix(web): open documents no longer reload after every agent command by @juliusmarminge in pingdotgg/t3code#32693 * fix(desktop): AppImage quit no longer leaves zygotes running on an unmounted image by @juliusmarminge in pingdotgg/t3code#36057 * fix(server): a message sent after a stopped worktree setup waits instead of running in the project checkout by @Gourav224 in pingdotgg/t3code#23618 * test(server): terminal checkout test waits for the refresh's last event by @juliusmarminge in pingdotgg/t3code#36071 ## New Contributors * @tnfssc made their first contribution in pingdotgg/t3code#35728 * @linushohne made their first contribution in pingdotgg/t3code#31473 * @maschwenk made their first contribution in pingdotgg/t3code#34061 * @dpreil-ydlp made their first contribution in pingdotgg/t3code#31426 **Full Changelog**: pingdotgg/t3code@v0.1.10-nightly.20261113.4616...v0.1.10-nightly.20261113.4630 </details> Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.1.10-nightly.20261113.4630
The review item is correct. The previous head lowercased the whole Content-Type header and used includes("text/event-stream"). A JSON response with
Content-Type: application/json; profile="https://example.com/Text/Event-Stream"was therefore routed to the SSE reader and yielded no payload, which is worse than main. responsePayloads now takes only the media type before the first ';', trims it, and compares it case-insensitively with equality to 'text/event-stream'. This keeps the original fix (Text/Event-Stream; charset=utf-8is read as SSE, per RFC 9110 case-insensitive media types) and stops parameters from selecting the reader. No regex or parser was added: split(';', 1), trim and toLowerCase are linear in the header length. The reviewer's case was added to the existing mixed-case test in mcpResponsePayloads.test.ts (renamed to 'picks the SSE reader from the media type alone, whatever its case'). It failed on the old head and passes now.Fixes #35792
Reproduction on main
On origin/main 6f21b0f with locked deps installed (vp i), ran the reporter's repro-media.mjs (copied from the issue) against the checkout under Node v26.13.1 / effect 4.1.3. It drives the production mcpToolSession from apps/server/src/mcp/AcpMcpStdioBridge.ts with a fake fetch. Output: 'lowercase SSE: tool result ok', then 'mixed-case SSE: Unexpected token 'd', "data: {"js"... is not valid JSON', 'RESULT: defect reproduced', exit 1. Also added a unit test in packages/shared/src/mcpResponsePayloads.test.ts feeding a Response with content-type 'Text/Event-Stream; charset=utf-8' and body 'data: {"id":1}\n\n';
vp test runfails on main with the same JSON.parse error.Evidence
Run the production
acp-mcp-call echo {}CLI against a real loopback HTTP MCP fixture. Initialize with JSON, acknowledge initialization with HTTP 202, then return the same SSE tool result using lowercase, mixed-case, and mixed-case with charset Content-Type values. Environment on both sides: Linux x86_64, Node v26.12.1, locked dependencies, synthetic data only.Before transcript · After transcript · Reproduction driver
Not shown:
Tests
vp test run packages/shared/src/mcpResponsePayloads.test.ts: with only the new assertion added (before the code change) 1 failed ('expected [] to deeply equal [ { jsonrpc: "2.0", id: 1, … } ]'); after the fix 2/2 passed. vp run check:changed: format, lint, knip 1/2 and 2/2, typecheck (35 packages), and test (2 files) all pass.
Not in this change
Who did the work
Run by the t3code factory in T3 Code.