Skip to content

fix(agent): also strip bare <invoke> tool-call blocks (no function_calls wrapper) - #1261

Merged
mrgoonie merged 1 commit into
nextlevelbuilder:devfrom
zezaeoh:fix/sanitize-bare-invoke-block
Jun 23, 2026
Merged

fix(agent): also strip bare <invoke> tool-call blocks (no function_calls wrapper)#1261
mrgoonie merged 1 commit into
nextlevelbuilder:devfrom
zezaeoh:fix/sanitize-bare-invoke-block

Conversation

@zezaeoh

@zezaeoh zezaeoh commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Problem

Follow-up to #1260. That PR removes a complete <function_calls>...</function_calls> block a model emitted as text. But in production the claude-cli proxy (under a degraded/bloated session) emits the tool call as a bare <invoke name="...">...</invoke> block with no <function_calls> wrapper:

Format error, retrying.
<invoke name="mcp__goclaw-bridge__exec">
<parameter name="command">( cd ~/path && gh pr view 1218 )</parameter>
</invoke>

fullToolCallBlockPattern (wrapper-only) misses this, so it falls through to the tag-only strip — which deletes the tags but leaks the inner <parameter> command text into the reply, while the tool still never runs (silent no-op).

Fix

  • Add bareInvokeBlockPattern and remove complete bare <invoke>...</invoke> blocks too, after the wrapped form (so wrapper-nested invokes aren't double-handled).
  • Add <invoke name= to garbledToolXMLIndicators so even a bare invoke triggers the cleanup gate.
  • Both block types feed the single dropped text-encoded tool call WARN with the attempted tool name(s).

Unterminated/partial artifacts still fall through to the existing tag-level strip (DeepSeek/GLM/Minimax behavior unchanged). No effect on providers that emit proper structured tool calls.

Test

internal/agent/sanitize_toolcall_xml_test.go extended:

  • bare <invoke> block removed, surrounding prose kept
  • bare-invoke-only response collapses to empty
  • SanitizeAssistantContent does not leak the bare-invoke command argument (regression)
go build ./...                     ✅
go build -tags sqliteonly ./...     ✅
go vet ./internal/agent/            ✅
go test -race ./internal/agent/     ✅

No migration / schema / i18n change.

Follow-up to nextlevelbuilder#1260. A model (notably the claude-cli proxy under a
degraded session) sometimes emits a tool call as a bare
<invoke name="...">...</invoke> block with no <function_calls> wrapper.
fullToolCallBlockPattern only matched the wrapped form, so the bare
block slipped through to the tag-only strip, leaking the inner
<parameter> command text into the reply while the tool never ran.

Match and remove complete bare <invoke> blocks too (after the wrapped
form), add "<invoke name=" as a detection indicator, and log the
dropped tool name(s). Partial/unterminated artifacts still fall through
to the existing tag strip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@mrgoonie mrgoonie left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary: This is a focused follow-up to the wrapped function_calls sanitizer. It removes complete bare ... text blocks before the fallback tag stripping path, so command parameters do not leak into user-facing replies when a provider emits a tool call as text.

Risk level: Low

Mandatory gates:

  • Duplicate/prior implementation: clear; search only found this PR for the bare-invoke no-wrapper case, while prior work handled the wrapped block form.
  • Project standards: passed; the change stays in internal/agent sanitizer code and adds targeted regression coverage.
  • Strategic necessity: clear value; this closes a real leakage/no-op failure mode in degraded tool-call output handling.
  • CI/checks: green.

Findings:

  • Critical: none.
  • Important: none.
  • Suggestion: none.

Verdict: APPROVE

Reviewed evidence: diff limited to internal/agent/sanitize.go and internal/agent/sanitize_toolcall_xml_test.go; CI release-versioning, go, and web checks are successful; added tests cover bare invoke removal and command-argument non-leakage.

Posted by /github-maintain at 2026-06-23T00:00:00Z

@mrgoonie
mrgoonie merged commit d8cc954 into nextlevelbuilder:dev Jun 23, 2026
3 checks passed
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.

2 participants