Skip to content

fix(core): strip NUL bytes from shell tool text on capture and replay - #58384

Closed
OrangerXu wants to merge 1 commit into
anomalyco:devfrom
OrangerXu:fix/nul-bytes-shell-output
Closed

OrangerXu wants to merge 1 commit into
anomalyco:devfrom
OrangerXu:fix/nul-bytes-shell-output

Conversation

@OrangerXu

Copy link
Copy Markdown

Fixes #58354.

Capture layer — the shell tool strips NUL bytes where the captured output becomes tool text. Shell output can legitimately carry NUL (a git-uploadpack capability advertisement is HEADmulti_ack thin-pack ...), and a NUL is never meaningful in the text an agent reads — but captured verbatim it lands in session history.

Replay layer — the to-LLM boundary scrubs NUL from tool text again, so a session that was recorded before this fix and is already poisoned stops failing: every request replayed the byte, Venice answered String must not contain null bytes with a 400, and the issue's recovery path was the revert API. With this layer the poisoned session simply works.

Together: the turn with the tool call still succeeds (as before), and every later prompt succeeds too.

Test: a replay case in session-runner-message.test.ts feeds a completed bash tool whose text carries HEADmulti_ack and asserts the lowered messages contain no NUL while the readable text survives — 30/30 in the file. The wider core suite's shell/session failures on this Windows machine are pre-existing (50 of them on the untouched base; they spawn real shells) and unrelated to this change.

Shell output can legitimately carry NUL bytes (a git-uploadpack capability
advertisement is HEAD\0multi_ack ...). Captured verbatim, they land in
session history, and providers that reject NUL in request strings (Venice)
then refuse every later replay of the session with 'Invalid request
parameters' - the session is effectively bricked and recovery needed the
revert API (anomalyco#58354).

Two layers: the shell tool strips NUL where the capture becomes tool text
(a NUL is never meaningful in the text an agent reads), and the
replay-to-LLM boundary scrubs tool text again, so sessions recorded before
the capture-side fix work again instead of needing a revert.

Fixes anomalyco#58354
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Oct 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 72 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@opencode-agent opencode-agent Bot closed this Oct 23, 2026
@opencode-agent

Copy link
Copy Markdown
Contributor

Thanks for working on this, and for the clear explanation of the capture and replay layers!

I think this duplicates #58361, which also fixes #58354. It removes NUL bytes in sanitizeSurrogates (packages/ai/src/utils/sanitize.ts). That function already runs on every string of every outgoing request, so the fix applies to all providers and to sessions that already contain a NUL. It also covers ! shell output and failed-tool error text, not just the shell tool.

If you think something is missing there, such as removing NULs from the shell output before it's stored, please comment on or test #58361. Also, stripNul in this PR has a literal NUL character in the source, which makes git treat both files as binary. An escaped "\u0000" avoids that.

@OrangerXu

Copy link
Copy Markdown
Author

Acknowledged — #58361's approach in sanitizeSurrogates is the better layer: it already runs on every string of every outgoing request, so it covers all providers, already-poisoned sessions, ! shell output and failed-tool error text at once, which my two capture/replay sites only approximated. Closing in favor of it.

Good catch on the literal NUL in stripNul making git treat the files as binary, too — for the record the escaped form that avoids it is "�". Leaving a review on #58361 with one verification note.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs:compliance This means the issue will auto-close after 2 hours.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

server: shell tool output containing NUL bytes bricks the session with "Invalid request parameters"

1 participant