fix(scratch): bound a search by bytes, not only by match count #1016
No reviewers
Labels
No labels
move-to-repo
coilyco-bridge-deploy
move-to-repo
coilyco-flight-deck-agent-compose
move-to-repo
coilyco-gaming-eco-app
move-to-repo
coilysiren-inbox
move-to-repo
unknown
🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
c#
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-gaming/sirens-echo!1016
Loading…
Reference in a new issue
No description provided.
Delete branch "aos/claude/bound-scratch-search-2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes the half of #940 that lives in this repo. Stacked on #1014, which unblocks a red
main; a branch offmaincannot pass its own gate until that lands.Ask 2 is already implemented, and that is the more useful finding
#940 asks to "render the reachable host set into the fetch tool's description so a refusal is predictable rather than discovered," and says "I would start there."
It is already there, in
internal/community/fetch.go:Landed in
4795f1con 2026-08-13, five days before the 2026-08-18 incident window. So the model was told the allowlist, in the tool schema, and fetched unreachable hosts anyway.That does not make the finding wrong: six fetches returning 56, 130, 130, 130, 24, 24 bytes is real waste. It makes the proposed fix wrong, and the remaining option ("filter at the search boundary") belongs to the exa MCP server rather than this repo. I did not implement it here, and I would not guess at it without measuring what the model actually did with the allowlist it was given.
Ask 1, fixed where it is this repo's to fix
scratch_searchwas bounded bymaxScratchMatches, a count of matching lines and nothing else. A hundred matches from long lines is arbitrarily large, which is how it returned 131,343 bytes into a 16,384 byte consumer with 114,959 dropped.Now bounded at the source:
maxToolResultBytes, the same budget the consumer holds. The walk stops rather than collecting and discarding.SIRENS_ECHO_SCRATCH_MATCH_RUNES(240). A minified file is one line, so without this a single hit could be the whole result.scratch_readis untouched. Trimming a file the model explicitly asked to read loses the same information at the source that it loses downstream, so the win is not the same, and #940's own framing is about results that were "paid for and then dropped".list_channel-messageand the forgejolist_*family are MCP servers, not this repo.Reproduced before fixing
The test corpus returns 168,539 bytes without these bounds, the same shape as the 131,343 recorded in production. Checked by reverting each bound separately:
Three tests: the byte bound trips below the match cap, one long match cannot be the whole result, and an ordinary small search is unchanged and claims no truncation — which is the assertion that stops the bound quietly eating normal answers.
On acceptance
#940 asks for "no single tool result above 32KB before bounding". This delivers that for
scratch_search, which is the largest offender in the record, and cannot deliver it for the MCP servers from inside this repo. The acceptance is not fully met by this PR and the remainder is not mine to close.Verification
just gatepasses: build, policy-check, vet, test, test-skips, pre-commit.