Skip to content

fix: use text blocks in recall follow-up to prevent tool leaking to client#332

Merged
BYK merged 1 commit into
mainfrom
fix/recall-followup-text-blocks
May 15, 2026
Merged

fix: use text blocks in recall follow-up to prevent tool leaking to client#332
BYK merged 1 commit into
mainfrom
fix/recall-followup-text-blocks

Conversation

@BYK
Copy link
Copy Markdown
Owner

@BYK BYK commented May 15, 2026

Summary

  • Fixes "model called invalid tool: recall" on the client side. PR fix: keep recall tool in follow-up request to prevent API rejection #330 kept recall in the follow-up tools list, which allowed the model to call it again. The follow-up response is piped raw (no RecallAwareAccumulator), so a second recall tool_use leaked through to OpenCode, which rejected it as an unknown tool.
  • Replaces tool_use/tool_result with plain text in follow-up messages. The assistant message gets a marker text (📚 Searching...) instead of the raw tool_use block, and the user message gets recall results as plain text instead of tool_result. This allows stripping recall from the tools list without violating the API constraint that tool_use must reference a defined tool.
  • Adds defense-in-depth recall suppression to both streaming and non-streaming follow-up response paths, so even if recall somehow appears in the continuation it gets stripped before reaching the client.

Root cause

The recall follow-up has a 3-way constraint:

  1. Can't keep recall in tools → model calls it again, follow-up is piped raw, tool_use leaks to client
  2. Can't strip recall from tools while keeping tool_use in messages → API rejects (tool_use references undefined tool)
  3. Solution: use plain text blocks instead of tool_use/tool_result → no tool reference constraint, recall stripped from tools

Files changed

File Change
packages/gateway/src/recall.ts Rewrite buildRecallFollowUp() to use text blocks instead of tool_use/tool_result, strip recall from tools
packages/gateway/src/pipeline.ts Add defense-in-depth recall suppression in streaming + non-streaming follow-up paths
packages/gateway/test/recall.test.ts Update all buildRecallFollowUp test assertions for text block format, add empty-result test

…lient

The previous fix (#330) kept recall in the follow-up tools list, which
allowed the model to call recall again. The follow-up response is piped
raw without recall interception, so a second recall tool_use leaks
through to the client (OpenCode), which rejects it as an unknown tool.

Fix: replace tool_use/tool_result with plain text blocks in the follow-up
messages. The assistant message gets a marker text instead of the raw
tool_use, and the user message gets recall results as plain text instead
of tool_result. This eliminates the API constraint (tool_use must
reference a defined tool) and allows stripping recall from the tools list
to prevent re-invocation.

Also add defense-in-depth recall suppression to both streaming and
non-streaming follow-up response paths.
@BYK BYK merged commit 8f7b0f3 into main May 15, 2026
7 checks passed
@BYK BYK deleted the fix/recall-followup-text-blocks branch May 15, 2026 10:57
This was referenced May 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant