Repository navigation
[Bug]: Recognize reordered and escaped freeform wrapper keys in direct SSE previews #5151
Description
Client or integration
Direct HTTP/API client
Area
Streaming
Summary
Direct Responses SSE can publish raw JSON for a freeform wrapper with a reordered or escaped input property, then complete with its unwrapped value. For example, {"metadata":1,"input":"cmd"} and {"\u0069nput":"cmd"} can preview JSON and complete as cmd.
This is present before #5129, which extracts the existing decoder and repairs routed preview behavior. The routed restoration path suppresses these unchanged object prefixes; this issue covers the remaining direct bridge path. Related: #5047, #5113, and the review discussion.
Reproduction
Static source trace at 4e7d7132d8f8a7d46e460b48c5104a6d5ced4567; no local runtime reproduction was executed.
- Declare a custom freeform
exectool on the direct Responses bridge. - Feed a fake upstream function call whose complete arguments are either example above, across every split boundary and characterwise chunks.
- Capture the custom-tool input deltas and final item input.
- The literal-first-property matcher returns unrecognized object text unchanged, while completion parses the object and extracts
input. Assert that preview never publishes wrapper syntax that completion removes.
Version
4e7d713; still present at PR #5129 head b1b604e.
Operating system
Source review on macOS; the decoder is platform-independent. Execution proof must come from hosted CI.
Provider and model
Not provider-specific. Use a fake upstream without credentials.
Logs or error output
No live traffic or runtime logs collected. This report is based on the source trace and existing contract tests.
Screenshots and supporting files
- Existing literal matcher and raw fallback.
- Completion's JSON parsing.
- Extracted helper and routed object hold.
- Existing byte-exact ambiguous-object regression.
- RFC 8259: objects are unordered collections; property names are strings whose characters can be escaped. Retrieved 2026-09-19.
Redacted configuration
A custom freeform exec declaration, a fake upstream event fixture and an isolated translator budget. No live provider, user state directory or credentials are required.
Implementation path
- Replace literal-only detection at the shared progressive decoder with bounded JSON object scanning. Track strings, escapes, nested values and top-level property boundaries. Decode property names according to JSON rules and recognize canonical
inputafter preceding properties. - Hold uncertainty until classification is possible. Preserve completion's own-
inputprecedence and exactly-one-string-fallback-candidate rule. If no wrapper applies, release the original bytes as deltas rather than permanently suppressing ambiguous raw objects. - Preserve fence/patch holds, tool-name ownership, caller-owned accounting and terminal/disposal release. Update the transport owner documentation with the precise buffering policy.
- Add sibling focused regressions and register new files in both test-layout inventories. Do not grow a capped bridge test file or raise the ratchet.
Acceptance criteria and verification
- Reordered and escaped canonical keys are covered across every split and characterwise chunks; preview and authoritative completion agree without exposing wrapper syntax.
{"code":"a","script":"b"}and{"code":1}retain original raw delta and final bytes once classified as non-wrappers.- Canonical ordinary input and plain raw bodies remain progressive where their classification is safe.
- Cover nested preceding values, escaped fallback keys, non-string
input, tool-name negatives, cancellation and buffer release. - Retain explicit duplicate-key and late-invalid-input limitations rather than claiming a universal invariant unsupported by the buffering policy.
- Hosted regression execution and exact-head applicable CI are required; this issue claims no passing runtime result.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
fix(responses): recognize reordered and escaped freeform wrapper keys
fix(responses): recognize reordered and escaped freeform wrapper keys
fix(responses): recognize reordered and escaped freeform wrapper keys
fix(streaming): carry three transport hardening PRs and close the reo…
lidge-jun commentedon Sep 19, 2026
Closed by #5203, squash-merged into
devas4853f90d9a.The direct SSE preview compared the wrapper prefix as a literal, so a freeform payload whose keys arrived reordered — or whose key was escaped — was invisible to it. That comparison is now a JSON scan (
src/responses/freeform-wrapper-scan.ts), and the reordered and escaped cases are covered at every split point along with the case where the budget is exceeded and nothing is published.One consequence is worth stating plainly rather than leaving for someone to discover. A parseable JSON object that is not a wrapper now leaves the direct bridge in one piece when the object closes, instead of streaming through. This is unavoidable:
inputmay follow any property, so bytes emitted before the object closes can be contradicted by the completion that follows. The routed path has held the same body since #5047, so the two paths converge rather than diverge. Non-object bodies — exec programs, patch envelopes — are unaffected. The cost is recorded instructure/transports/responses.md, and the bridge test comments that asserted the old timing were corrected rather than left to mislead.Adversarial review changed the release shape here too, and the reviewer's prescription was better than the first attempt: the over-budget buffer no longer re-parses on every delta, and a release parse is allowed only when the scanner has actually seen the object close. A hold short on buffer and a hold short on budget both stay held. The reasoning is that a delta an over-budget buffer would have emitted arrives at the same moment as the authoritative completion behind it, so it buys the client nothing. There is a regression pinning that with a value made of 4097 closing braces.
Three transport hardening carries landed in the same PR with their authors credited, and none of them went in as submitted. The varint length bound in the Grok coupon reader did not actually exist, because continuation bytes carrying no payload contribute zero and never trip the safe-integer guard — twenty
0x80bytes followed by0x00decoded as a well-formed zero, and an overlong zero length normalizes a malformed response into an empty coupon list. The explicit 10-byte protobuf bound is now there with a regression. The Devin reasoning-signature carry, as submitted, preserved pairing by discarding every reasoning block but the last, which loses real text when several blocks are signed; text is now replayed in full and the signature is attached only when the text being sent is the text that signature vouches for. The hosted-tool index carry was correct but its regression could not tell "first stripped container" from "index 0", since every container in the fixture was stripped.Evidence is exact-head hosted CI at
7aa30697f3159b69a7c6579d43ad5708b7f2175c:test 1/4through4/4andgatessuccessful.test 3/4matters specifically — before the file-size ratchet ondevwas repaired it aborted at batch 3 of 29 and the remaining 26 never ran, which lefttests/providers/devin-hardening.test.tsunexecuted; it now runs to completion. No local suite was run in this lane.