Skip to content

Guard against empty tool-result content on bare-command re-run - #34

Merged
yonilerner merged 1 commit into
yonilerner:mainfrom
mmarcuccio:fix/empty-tool-result-content
Aug 11, 2026
Merged

yonilerner merged 1 commit into
yonilerner:mainfrom
mmarcuccio:fix/empty-tool-result-content

Conversation

@mmarcuccio

@mmarcuccio mmarcuccio commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

Anthropic returns HTTP 400 with "tool_result: content cannot be empty if is_error is true" whenever a tool result block has isError: true and no text content. Because pi replays full history on every turn, one such record permanently bricks the session. The only recovery today is hand-editing the session JSONL.

The trigger is the sandbox-escape re-run path. When the user presses Esc during an approved escape (e.g. a git push), runBareCommand SIGKILLs the process group, so the killed command writes nothing to either stream and resolves with exitCode: null, signal: "SIGKILL". The old code at extensions/pi-auto.ts:436 produced exactly the rejected shape:

-text: bare.stdout + (bare.stderr ? `\n[stderr]\n${bare.stderr}` : ""),
-isError: bare.exitCode !== 0,
+const { text, isError } = bareExecResultToToolContent(bare);

Changes

  • extensions/sandbox.ts — exports bareExecResultToToolContent(result) which formats stdout/stderr as before but substitutes a descriptive placeholder (naming the kill signal when present) whenever combined output is empty
  • extensions/pi-auto.ts — uses the helper at the escape re-run site
  • tests/sandbox.test.ts — five new cases covering the normal path, empty+non-zero, empty+SIGKILL, and empty+success

Anthropic's API returns HTTP 400 with "tool_result: content cannot be
empty if is_error is true" when a tool result block has isError=true and
no text content. Because pi replays full conversation history on every
turn, one such record permanently bricks the session — every subsequent
prompt 400s until the JSONL is hand-edited.

The trigger is the sandbox-escape re-run path: when the user presses Esc
during an approved escape (e.g. a git push), runBareCommand SIGKILLs the
process group, so the killed command writes nothing to either stream and
resolves with exitCode=null, signal='SIGKILL'. The old code produced
text: "" + isError: true — exactly the rejected shape.

Fix: add bareExecResultToToolContent() in sandbox.ts that substitutes a
descriptive placeholder whenever combined stdout+stderr is empty (naming
the kill signal when one is present). Use it at the escape re-run site in
pi-auto.ts.
@mmarcuccio
mmarcuccio marked this pull request as ready for review August 5, 2026 16:33
@yonilerner
yonilerner enabled auto-merge (squash) August 6, 2026 19:51
@yonilerner
yonilerner disabled auto-merge August 10, 2026 22:25
@yonilerner
yonilerner enabled auto-merge (squash) August 10, 2026 22:26
@yonilerner
yonilerner disabled auto-merge August 11, 2026 19:31
@yonilerner
yonilerner merged commit ad38e02 into yonilerner:main Aug 11, 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.

2 participants