Skip to content

fix(agents): strip tool_call/tool_result XML leaking into channel messages#42260

Closed
jnMetaCode wants to merge 1 commit into
openclaw:mainfrom
jnMetaCode:fix/strip-tool-call-xml-40879
Closed

fix(agents): strip tool_call/tool_result XML leaking into channel messages#42260
jnMetaCode wants to merge 1 commit into
openclaw:mainfrom
jnMetaCode:fix/strip-tool-call-xml-40879

Conversation

@jnMetaCode

Copy link
Copy Markdown
Contributor

Summary

  • Add stripToolCallXml() to strip raw <tool_call> / <tool_result> XML blocks that some models emit as plain text
  • Integrate into the extractAssistantText() sanitization chain alongside existing stripMinimaxToolCallXml() and stripModelSpecialTokens()

Problem

When a model outputs raw XML tool markup (e.g. <tool_call>{"name": "subagents", ...}</tool_call>), these blocks leak into user-facing channel messages (Discord, Telegram, etc.) because no sanitization function targets them.

Existing strippers handle:

  • stripMinimaxToolCallXml(): MiniMax-specific <invoke> / </minimax:tool_call> tags
  • stripDowngradedToolCallText(): text-based [Tool Call: ...] markers
  • stripModelSpecialTokens(): model special tokens

But generic <tool_call> / <tool_result> XML is not covered.

Fix

New stripToolCallXml() function with:

  • Fast-path regex guard (skips text without tool tags)
  • Non-greedy block removal for paired <tool_call>...</tool_call> and <tool_result>...</tool_result>
  • Stray tag cleanup for unclosed/orphaned tags

Test plan

  • Unit tests for stripToolCallXml() (7 cases: paired blocks, attributes, stray tags, empty input, no-op)
  • Integration test for extractAssistantText() with tool_call XML

Closes #40879

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S labels Mar 10, 2026
@greptile-apps

greptile-apps Bot commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds stripToolCallXml() to sanitize raw <tool_call> / <tool_result> XML that some models emit as plain text, and wires it into the extractAssistantText() sanitization chain between stripModelSpecialTokens and stripDowngradedToolCallText. The implementation is clean, correct, and consistent with the existing stripMinimaxToolCallXml pattern.

Key changes:

  • New stripToolCallXml() in pi-embedded-utils.ts: fast-path regex guard, non-greedy paired-block removal, stray-tag fallback cleanup.
  • Integration in extractAssistantText(): placement in the chain is sensible — after MiniMax-specific and special-token stripping, before downgraded-text and thinking-tag stripping.
  • 7 unit tests + 1 integration test covering paired blocks, attributed tags, stray/unclosed tags, empty input, no-op, and end-to-end through extractAssistantText.

One style note: Stripping whole-line <tool_call> / <tool_result> blocks leaves the surrounding newlines intact, resulting in consecutive blank lines in user-facing output (e.g. \n\n\n between surrounding text). This is consistent with stripMinimaxToolCallXml's behavior and is documented in the test, but a shared post-pass that collapses 3+ newlines to 2 in extractAssistantText would improve message presentation across all strippers.

Confidence Score: 4/5

  • Safe to merge; the new stripper is correct, well-tested, and follows established patterns with no risk of breaking existing behaviour.
  • The implementation is straightforward regex-based text sanitization with no side-effects, proper fast-path guards, and non-greedy patterns that avoid ReDoS. Tests cover the important cases. The only minor concern is cosmetic (surplus blank lines in stripped output) rather than a correctness or security issue.
  • No files require special attention.

Last reviewed commit: 23c1173

Comment thread src/agents/pi-embedded-utils.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23c117334c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/pi-embedded-utils.ts
@jnMetaCode
jnMetaCode force-pushed the fix/strip-tool-call-xml-40879 branch from 23c1173 to 503f969 Compare March 10, 2026 15:38

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 503f969f4f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/pi-embedded-utils.ts
@jnMetaCode
jnMetaCode force-pushed the fix/strip-tool-call-xml-40879 branch from 503f969 to 135985a Compare March 10, 2026 17:38

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 135985a9da

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/pi-embedded-utils.ts
@jnMetaCode
jnMetaCode force-pushed the fix/strip-tool-call-xml-40879 branch from 135985a to 77c18b9 Compare March 11, 2026 17:16

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 77c18b93da

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/pi-embedded-utils.ts
@jnMetaCode
jnMetaCode force-pushed the fix/strip-tool-call-xml-40879 branch from 77c18b9 to 65bead0 Compare March 12, 2026 13:43
@jnMetaCode
jnMetaCode force-pushed the fix/strip-tool-call-xml-40879 branch from 65bead0 to 27c6f74 Compare March 16, 2026 09:16

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 27c6f7485c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +27 to +28
let cleaned = text.replace(/<tool_call\b[^>]*>[\s\S]*?<\/tool_call>/gi, "");
cleaned = cleaned.replace(/<tool_result\b[^>]*>[\s\S]*?<\/tool_result>/gi, "");

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.

P2 Badge Match closing tool tags with trailing whitespace

The block-removal regexes require an exact </tool_call> / </tool_result> closer, so XML variants like </tool_call > (valid with whitespace before >) are not removed as blocks. In that case the later stray-tag cleanup deletes only the tags and leaves the inner tool JSON/result text in the user-visible message, which defeats the leak-prevention this sanitizer is intended to provide for model-emitted XML.

Useful? React with 👍 / 👎.

@jnMetaCode

Copy link
Copy Markdown
Contributor Author

Re: trailing whitespace in closing tags — the existing stripMinimaxToolCallXml regexes also match exact closing tags without trailing whitespace tolerance. These leaked XML tags are generated by specific model versions in a consistent format. Adding \s* before the closing > would broaden the regex beyond what's observed in practice. If a model variant starts emitting trailing spaces, we can tighten the regex then.

@jnMetaCode

Copy link
Copy Markdown
Contributor Author

Friendly ping — cleaned up my PR backlog to make review easier. This fixes tool_call/tool_result XML leaking into user-visible channel messages (reported in #40879). The approach mirrors the existing stripMinimaxToolCallXml pattern. Happy to rebase when CI is green.

@steipete

Copy link
Copy Markdown
Contributor

Closing this as implemented after Codex review.

Current main already strips leaked <tool_call> / <tool_result> XML on the canonical assistant-visible text path, with broader coverage than this PR, and that implementation is already present in release v2026.4.22.

What I checked:

  • Canonical sanitizer already strips tool-call XML: stripToolCallXmlTags() is implemented as a stateful sanitizer that removes <tool_call> / <tool_result> payloads, handles truncated blocks, and avoids early close on quoted </tool_call> text. (src/shared/text/assistant-visible-text.ts:199, 7d9172d5e093)
  • Sanitizer is wired into the user-visible extraction pipeline: The canonical assistant-visible pipeline applies stripToolCallXmlTags() before downgraded-tool stripping, and extractAssistantText() routes assistant text through sanitizeAssistantVisibleText(). Session/history extraction also uses the shared sanitizer profile. (src/shared/text/assistant-visible-text.ts:581, 7d9172d5e093)
  • Tests cover the reported leak and stricter edge cases: Current tests cover closed <tool_call>/<tool_result> blocks, truncated openings, mismatched closers, quoted </tool_call> inside JSON strings, and the combined canonical sanitizer path. (src/shared/text/assistant-visible-text.test.ts:108, 7d9172d5e093)
  • Shipped in latest release: The same assistant-visible sanitizer implementation is present at release tag v2026.4.22 (00bd2cf7a376f1fba26291c6c4766f1f15cbdfa5), so the fix is already shipped. (src/shared/text/assistant-visible-text.ts:199, 00bd2cf7a376)

So I’m closing this as already implemented rather than keeping a duplicate issue open.

Review notes: reviewed against 7d9172d5e093; fix evidence: release v2026.4.22, commit 00bd2cf7a376.

@steipete steipete closed this Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Internal <tool_call> / <tool_result> blocks leak into Discord messages

2 participants