Malformed tool-call emission silently swallows execution + notification (post-background-completion wake-stall)
Summary
A malformed function-call emission — where the model's tool call serializes as literal assistant text with a corrupted wrapper token instead of a real antml function-call block — is silently dropped by the harness: nothing executes, no result block is returned, and no completion/error notification fires. When this happens on the continuation turn that wakes a session after a background task completes, the session goes idle again with no signal. From the operator's view, "Claude woke and then never did anything" — a silent wait-deadlock with no error, no timeout, no second ring.
Impact
- Severity: high for autonomous/background workflows. Long autonomous runs dispatch work as background tasks (
run_in_background:true) and rely on the completion notification to resume. If the resume turn's first tool call garbles, the run silently stalls and a human must notice and poke it. - No layer can self-recover: PreToolUse/PostToolUse hooks never fire (a malformed emission is not a tool invocation); the model receives no "empty result" event to react to (from its view it made the call and the turn ended); external task-watchdogs have no task ID to monitor (the background task already completed — the stall is in the consumer turn).
Reproduction (observed instance)
- Session dispatches a Bash command with
run_in_background:true, ends its turn, awaits the<task-notification>. - The background task completes; the notification fires; the wake/continuation turn begins.
- The wake turn's first tool call (a
cat <output-file>read) renders as assistant TEXT with a stray wrapper token where the function-call block opening should be:
````
court
<invoke name="Bash">
<parameter name="command">cat /private/tmp/.../tasks/<id>.output 2>/dev/null | tail -6
...</parameter>
</invoke>
(Note the literal court token instead of the correct antml opening; the inner invoke/parameter shape is otherwise intact and the command content is valid.)
- The harness does not parse this as a tool call → executes nothing → returns no result → the turn ends → the session idles. Re-issuing the identical command on the next turn runs fine, confirming the content was valid and only the emission was malformed.
Expected behavior (any one would resolve the deadlock)
- Detect-and-error: when assistant output contains a function-call-shaped block that fails to parse as a real tool call, surface a visible error to the session (an "empty result" / parse-failure event the model can react to and re-issue), rather than rendering it as inert text.
- Notify on no-op wake: if a turn that began from a
<task-notification>ends without executing any tool call and without producing a user-directed message, emit a diagnostic/notification rather than silently idling. - Tolerant parsing: recognize and recover the near-miss wrapper token (e.g. the stray
court) so the intended call executes.
Environment
- Claude Code (CLI), macOS (darwin). Reproduced across multiple repos/sessions (dev-workflow, backlit) on 2026-06-17.
- Also observed in the Kimi runtime variant, suggesting the malformed-emission class is not specific to one model backend.
Local mitigations in place (not a substitute for the harness fix)
- Lead-side discipline: a background dispatch is "in flight" only once a real task ID appears in the result; an empty return means the call did not fire → re-issue immediately (tracked internally as PRJ-1817).
- A transcript-tailing detector that flags a function-call-shaped emission rendered as assistant text on a post-wake turn and alerts the operator (tracked internally as PRJ-1818). This is a backstop; the silent-swallow root cause is in the harness.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗