Skip to content

fix(web-search): budget batched results per query so none are dropped - #6638

Merged
lidge-jun merged 1 commit into
devfrom
codex/b2-search-truncation
Oct 5, 2026
Merged

lidge-jun merged 1 commit into
devfrom
codex/b2-search-truncation

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fixes #6621

When the routed model batched several queries into one web_search call, 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.

formatWebSearchResults now plans the 8,000-character budget per query before anything is serialized:

  • Every query keeps its [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.
  • Omissions are explicit and use original sizes: [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.
  • Structured output is always one valid JSON document. Each entry gains status, and shortened entries also carry answerTruncated/answerLength/omittedSources. The budget covers the whole returned string, including the preamble.
  • Batched query and error text is cut without splitting a surrogate pair.
  • A call with far more queries than one result can describe (the loops accept any number of queries and answer the extras with limit-reached outcomes) falls back to one status line per query with no web content. Queries that still don't fit are counted by status ([k more queries not listed: a succeeded, b could not run], or omittedQueries + omittedQueryStatus in 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:changed or prepush). Hosted CI at the exact head is the test evidence.

Static checks run locally on this head:

  • bun run typecheck: pass
  • bun run structure:check: pass (the structure/runtime.md sentence was added inside an existing paragraph; the file stays at 600 lines)
  • bun run privacy:scan: pass
  • bun 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

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed. (docs-site/.../guides/sidecars.md, structure/runtime.md)
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults. (No auth, credential or redaction change; the untrusted-content boundary is preserved.)

Summary by CodeRabbit

  • Improvements

    • Batched web searches now share an 8,000-character output limit. Results indicate when answers are shortened or sources are omitted, and oversized batches report query statuses and how many queries could not be listed.
    • Structured-output responses remain valid JSON, including when results must be condensed.
  • Documentation

    • Updated the sidecar guide to explain result limits, truncation, and status reporting.

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
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner October 5, 2026 13:37
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (2)
src/AGENTS.md — auto-discovered
structure/AGENTS.md — auto-discovered

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 377672f1-e5f5-4c6b-9241-8d98f09c9320
📥 Commits

Reviewing files that changed from the base of the PR and between 37189ca and 6d8712b.

📒 Files selected for processing (4)
  • docs-site/src/content/docs/guides/sidecars.md
  • src/web-search/format-result.ts
  • structure/runtime.md
  • tests/web-search/format-result.test.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.


📝 Walkthrough

Walkthrough

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

Changes

Batched web-search formatting

Layer / File(s) Summary
Prepare batch items and allocate output budget
src/web-search/format-result.ts (lines 10–25, 69–193)
The formatter bounds batch error text, avoids splitting surrogate pairs, prepares query outcomes, and allocates answer and source capacity across the shared budget.
Render batch output and validate limits
src/web-search/format-result.ts (lines 195–372), tests/web-search/format-result.test.ts (lines 40–145), docs-site/src/content/docs/guides/sidecars.md (lines 48–55), structure/runtime.md (line 401)
Prose and structured output report truncation and omissions. The formatter uses status-only fallbacks when fixed batch content exceeds the limit. Tests cover budget allocation, omitted content, structured validity, and single-result compatibility. Documentation describes the batch limit and fallback behavior.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 6d871

No identified batch-formatting issue remains that should block merge.

Security Architecture Review

Security architecture risk: ⚪ Minimal · up to 6d871

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
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The security-relevant exposure remains attacker-influenced search content delivered into the current answering turn through either search loop. More query outcomes can now remain visible within the existing cap, but the formatter grants no new programmatic credential, network, or persistence access.

Trust Boundaries and Controls

  • observed — The prose boundary remains advisory: answer text is inserted without delimiter escaping, and source lines sit outside the answer tags. Both behaviors predate this PR. The change preserves generated closing tags during budget fitting and adds batch error sanitization; it does not establish a new enforced isolation boundary.

Resilience and Maintainability Implications

  • observed — Oversized batches degrade to explicit status-only output rather than silently losing later results. Counts distinguish successful and failed unlisted queries, while successful listed queries retain original answer and source sizes, preserving visibility into omitted untrusted reference material.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning 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: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: allocating the shared result budget across batched queries so later results are not dropped.
Linked Issues check ✅ Passed Issue #6621 asks the formatter to keep batched queries represented within the 8,000-character limit and to mark omitted content. src/web-search/format-result.ts plans per-query answer and source bud…
Out of Scope Changes check ✅ Passed The reported changes are limited to src/web-search/format-result.ts, its focused regression tests, and web-search documentation in docs-site/src/content/docs/guides/sidecars.md and `structure/runt…
Full details: Docstring Coverage

Explanation

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

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Oct 5, 2026
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@lidge-jun

Copy link
Copy Markdown
Owner Author

Maintainer integration (lidge-jun, dev only) — 2026-10-05 bug triage round 2, lane B2.

  • Head: 6d8712b
  • CI at this head: PR CI 37318226364, enforce-target 37318480744; every executed job passed.
  • Review: fixes Batched web-search truncation silently drops later queries and sources #6621: per-query budget allocation before truncation, every query keeps status/source count, explicit omission markers outside untrusted-content tags, structured output always valid JSON.
  • No local tests were run, per owner instruction; hosted CI is the evidence.
  • scripts/ci/assert-mergeable-review.sh --maintainer-integration: OK.

@lidge-jun
lidge-jun merged commit 7e7468a into dev Oct 5, 2026
35 checks passed
@lidge-jun
lidge-jun deleted the codex/b2-search-truncation branch October 5, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant