Repository navigation
fix(web-search): budget batched results per query so none are dropped - #6638
Conversation
Batched web_search results were joined and then cut at 8,000 characters, dropping later queries and their sources, and the structured path sliced serialized JSON into an invalid document. Plan the budget per query before serialization, mark omissions with original sizes outside the untrusted boundary, keep structured output valid JSON, and condense to status lines with per-status tallies when a call has too many queries. Fixes #6621
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. 🧰 Additional context used📚 Code guidelines (2)No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (4)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review. 📝 WalkthroughWalkthroughBatched web-search results now share an 8,000-character output budget. The formatter allocates space across query outcomes, marks shortened or omitted content, and uses status-only output when the batch cannot fit. Structured output remains valid JSON. Single-result formatting remains separate. ChangesBatched web-search formatting
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix · Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to No identified batch-formatting issue remains that should block merge. Security Architecture ReviewSecurity architecture risk: ⚪ Minimal · up to The change preserves query outcomes and reports omitted content without adding execution paths or privileges. Structured batches remain complete JSON documents, and generated untrusted-content boundaries are no longer cut by a global output clamp. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 70.59% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 2 files. (2 skipped: 2 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
✅ Deterministic PR hygiene checks passed. |
|
Maintainer integration (lidge-jun, dev only) — 2026-10-05 bug triage round 2, lane B2.
|
Summary
Fixes #6621
When the routed model batched several queries into one
web_searchcall, the formatter joined every result block and then cut the string at 8,000 characters. Long answers pushed later queries out completely. In the issue's three-query repro, the third query and its source vanished, and the answering model reported that query as having "no usable results". The structured-output path had the same cut applied to an already serialized JSON document, so the payload could stop mid-string and fail to parse.formatWebSearchResultsnow plans the 8,000-character budget per query before anything is serialized:[i/n]label, its status, its boundary tags and its source count. The rest of the budget is divided across queries, and a short answer's unused share goes to the others. Within one query, the answer and the whole source lines each get at least half of its share, and the first citation is listed whenever the share can hold it. Source lines are never cut.[answer shortened: X of Y characters shown]and[k more sources not listed], plus a leading note. These proxy-authored markers sit outside<web_search_result>, the untrusted-content boundary stays intact, and query angle-bracket stripping is unchanged.status, and shortened entries also carryanswerTruncated/answerLength/omittedSources. The budget covers the whole returned string, including the preamble.[k more queries not listed: a succeeded, b could not run], oromittedQueries+omittedQueryStatusin JSON). A final check on the finished string guarantees the 8,000-character bound.The 0- and 1-result paths are byte-identical, and a batch that already fits renders as before, except that results past the existing 4,000-character answer cap and 8-source cap are now marked. This PR touches only the formatter, its test file and docs. The search execution files (
loop.ts,run-turn-loop.ts, executors) are unchanged.Verification
Per owner instruction, no local tests were run (no
bun run test,bun test,test:changedorprepush). Hosted CI at the exact head is the test evidence.Static checks run locally on this head:
bun run typecheck: passbun run structure:check: pass (thestructure/runtime.mdsentence was added inside an existing paragraph; the file stays at 600 lines)bun run privacy:scan: passbun scripts/file-size-ratchet.ts: pass (no new test file, so the layout registries are untouched)Throwaway render scripts (not tests) called the formatter directly: 6,000 randomized batches of 2–41 queries in prose and structured modes, with escape-heavy and emoji text, 2,048-character URLs, 256-character titles, more than 8 sources and long errors. They showed zero invariant violations (length ≤ 8,000, every query listed or counted, statuses preserved, valid JSON, balanced boundary tags with markers outside, no lone surrogates) and a maximum length of exactly 8,000. Batches of 60 and 200 queries condensed within budget with exact tallies.
An independent gpt-6.1-sol review audited the design and implementation. The first round failed on five findings (budget overrun with unbounded query arrays, unreserved preamble, a surrogate split in the query cut, unused allowance, thin tests), and the second round passed after all five were fixed.
New regression cases in
tests/web-search/format-result.test.ts: the issue repro, valid structured JSON with omission accounting, slack donation, fit-case and singular byte compatibility, surrogate-safe query cuts, condensed tallies for 120 queries, and maximum-size titles with surrogate-heavy errors.Checklist
docs-site/.../guides/sidecars.md,structure/runtime.md)Summary by CodeRabbit
Improvements
Documentation