Stray "call"/"court" token before tool calls; internal <invoke> XML printed as text instead of executing (Windows local + cloud Cowork)

Status Open
Maintainer reply None cached
Activity 10 comments · opened Jun 14, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Occasionally, instead of cleanly executing a tool call, Claude Code emits a stray plain-text token — "call" or "court" — and then prints its internal tool-call markup (<invoke name="..."><parameter ...>...</parameter></invoke>) to the user as literal text. The call does not run. Re-issuing the request usually works and no files are corrupted, so the harm is garbled output and an interrupted workflow rather than data loss.

What Should Happen?

The tool call should execute normally: no stray "call"/"court" token should appear, and the internal <invoke> markup should never be shown to the user as text. The action should run on the first attempt.

Error Messages/Logs

# Verbatim leak (cloud Cowork session, tool mcp__workspace__bash). The stray
# token (call / court) is emitted as plain text, immediately followed by the
# internal <invoke> XML rendered literally instead of executing. The block also repeats.

call
<invoke name="mcp__workspace__bash">
<parameter name="command">cd "/sessions/zen-inspiring-knuth/mnt/English Master Project" && grep -c "이미지키 ↔ AI회화 키 분리 상태" docs/ops/할일_리스트_마스터.md</parameter>
</invoke>

court
<invoke name="mcp__workspace__bash">
<parameter name="command">cd "/sessions/zen-inspiring-knuth/mnt/English Master Project" && grep -c "이미지키 ↔ AI회화 키 분리 상태" docs/ops/할일_리스트_마스터.md</parameter>
</invoke>

court
<invoke name="mcp__workspace__bash">
<parameter name="command">cd "/sessions/zen-inspiring-knuth/mnt/English Master Project" && bash tools/release_check.sh 2>&1 | tail -5</parameter>
</invoke>

Steps to Reproduce

Note: This is intermittent and not reliably reproducible on demand. It is not tied to any specific file or command — it appears to be triggered by model output formatting, not by a user action. Below is the pattern under which it has consistently appeared:

  1. Open a Claude Code session and work normally, issuing many tool calls over an extended session (it clusters later in long, tool-call-heavy sessions).
  2. Before a tool call, the model emits a stray plain-text token — "call" or "court" — instead of cleanly invoking the tool.
  3. The harness rejects the malformed call, and the model's internal <invoke name="..."> ... </invoke> XML is printed to the user as literal text instead of being executed.
  4. The same tool-call block is often emitted twice (repeated).
  5. Re-running the request usually succeeds; no files are corrupted. The impact is garbled output and an interrupted workflow, not data loss.

Observed across two independent environments (see Additional Information), which suggests the cause is model-side rather than environment-specific.

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

Claude 1.12603.1.0

Platform

Other

Operating System

Windows

Terminal/Shell

PowerShell

Additional Information

This is not specific to one machine or OS. The same behavior — a stray "call"/"court" token followed by the internal <invoke> XML being printed as text instead of executing — has appeared in two independent environments:

  1. Local: Claude Code on Windows 11 (PowerShell).
  2. Cloud: a Claude "Cowork" sandbox session (Linux), with the leaked block referencing mcp__workspace__bash and a /sessions/.../mnt/ path.

It has also been seen on more than one model (Fable and Opus). Because it spans different OSes, environments, and models, the cause appears to be model-side output formatting rather than any local setup.

It is intermittent — the large majority of tool calls are clean — and it tends to cluster later in long, tool-call-heavy sessions. Re-running the request normally succeeds and no files are corrupted. Screenshots attached.

View original on GitHub ↗

7 Comments

sjswerdloff · 2 months ago

I have seen this too. Macos. Very rare for me, but not one off

snakamura1108 · 2 months ago

Adding a more severe variant of this "court" / corrupted-tool-call behavior, on a newer version + Windows.

What I saw
Same root pattern (the tool-call opener token gets corrupted to court), but in my session it didn't stop at a single malformed <invoke> block — the court token went into a full decode loop. The assistant turn started with a normal, on-task paragraph (it was about to run a CLI command after some explanatory prose), then emitted:

court

court

court

...

repeated 12,831 times in a single turn (13,362 across the whole session). There was no trailing <invoke> XML in my case — just the bare court token looping. stop_reason was null, so it never reached a stop token and the turn was truncated mid-loop.

Trigger matches the existing reports

  • Long, tool-call-heavy session.
  • The break happened on the assistant turn right after explanatory prose that preceded an intended tool call — exactly the "tool call after prose" correlation noted in #60584 / #66153.

Environment

  • Platform: Windows (VS Code)
  • Claude Code version: 2.1.178
  • Model: claude-opus-4-8

This is newer than the earlier reports (2.1.143 / 2.1.165), so it persists across versions, and across macOS + Windows — consistent with this being model-side rather than environment-specific.

Workaround
Don't --resume the corrupted session — reloading the ~13k-token court context tends to re-trigger the loop. Archive/rename the session .jsonl, start fresh, and re-seed only the last coherent state.

usedhonda · 2 months ago

Confirming the same behavior on macOS (so this now spans Windows + Linux + macOS), with Opus in Claude Code, during a long tool-call-heavy session.

Symptoms matched exactly:

  • A stray plain-text token (court in my case) right before the tool call
  • The internal <invoke ...> XML printed as literal text instead of executing
  • The same block emitted twice
  • Re-running succeeds; no data loss — just garbled output and an interrupted step
  • Clustered in the later part of a long, tool-heavy session

One observation that may help narrow it down: it tended to appear on turns where the model wrote prose immediately before emitting the tool call (explain-then-call in the same turn). Splitting "explain" and "call" into separate turns noticeably lowered the frequency, but did NOT eliminate it — so the root cause does look model-side, as noted.

0x000x7f · 2 months ago

Adding a data point from the Claude in Chrome MCP / browser-automation path — most reports here are Edit/Read/Bash/CLI, and the browser-MCP angle surfaces two consequences that don't seem captured yet.

Env: Windows 11, Claude Code hosted inside Claude Desktop (Cowork / local-agent mode), model claude-opus-4-8, browser automation via the Claude in Chrome MCP (cloud-relay peer path). Same trigger profile as above — long, tool-call-heavy session, and the corruption landed on turns that wrote explanatory prose right before the intended tool call (the "explain-then-call" correlation from #60584 / #66153 and the macOS comment above).

1. Silent stall, not just garbled text. When the stray court + a bare <invoke name="mcp__Claude_in_Chrome__computer"> (no antml: prefix) is emitted as text, the click/type never fires. With no parse error surfaced to the user — just un-executed text — the browser automation appears to hang mid-flow. It's easily misdiagnosed as a relay/connection problem rather than tool-call corruption; that cost me a while before I spotted the court token.

2. Recirculation loop (history + clipboard). The malformed block gets carried back into context and reappears on later turns as if it were input; the model then sometimes continues the broken narration, so the corruption is self-reinforcing — consistent with the in-context few-shot poisoning in #62344 and with the "don't --resume the corrupted session" advice above. In my case it also landed on the OS clipboard (the flow used the clipboard for field operations), giving it a second re-injection carrier. Clearing the clipboard alone did not stop it — the conversation-history echo was the primary channel.

Workarounds that worked: clear the OS clipboard (removes one carrier); start a fresh session / /clear and don't resume the poisoned context; restart the host (reinitializes the Cowork relay) — most reliable; or switch to the standalone CLI route (local native-host pipe, no cloud relay), which avoided it for me.

Net: looks like the same model-side token-generation defect, but on the browser-MCP path it turns "garbled output" into a silent automation stall plus a self-reinforcing loop.

gonnector · 2 months ago

We've been running a Stop-hook auto-recovery for this in production (Windows, Opus 4.8 1M) and came to share it — only to find @TheGreatCBH had already posted a nearly identical Stop-hook approach early on, over on the canonical issue #49747 (with a published repo). That's the good side of community echo: independently landing on the same fix is a decent signal it's the right shape. Sharing ours in the Windows thread anyway, since it differs in a couple of ways that matter for tool-heavy / multi-agent sessions.

Environment

  • Claude Code v2.1.187, Opus 4.8 (1M context), Windows
  • Most frequent in long / large-context sessions; persists across fresh sessions once a session is affected
  • Still reproducing as of 2.1.191 (per recent reports here), and I don't see a tool-call fix in the 2.1.188–2.1.193 release notes

Why a Stop hook and not PreToolUse: a leaked call never becomes a tool_use, so PreToolUse never fires. The hook inspects the last assistant message at turn end; if it finds leaked tool-call markup it blocks the stop with a reason that tells the model to re-issue the call correctly. The reason comes back as a system reminder and the model retries.

What's different in this one

  1. Catches partial leaks in multi-tool turns — it does not skip when a real tool_use is present in the same turn. In tool-heavy turns sometimes only some of several calls leak; gating on "no tool_use this turn" misses those.
  2. Active false-positive suppression — strips code fences and inline backticks before matching, so a turn that explains / quotes tool-call XML in prose won't trip it.

Plus MAX_RETRY=5, and on giving up it keeps the per-session counter so it stops blocking that episode (no infinite block) and the reason recommends a fresh session — because once the context is poisoned (#62344) retries just reproduce the same malformed output.

Hook~/.claude/hooks/stop-hook-toolcall-leak-retry.sh:

#!/bin/bash
# Stop hook — detects a tool call that leaked as plain text without the antml: prefix
# (e.g. `call <invoke name="Read">`) and blocks the stop with a reason that forces a
# correctly-formatted retry. Gives up after MAX_RETRY (recommends a new session).
# Stopgap for the Opus 4.8 + tool_use model-level regression. Remove once it's fixed.
MAX_RETRY=5

input=$(cat)
transcript=$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)
session=$(printf '%s' "$input" | jq -r '.session_id // "default"' 2>/dev/null)

[ -z "$transcript" ] && exit 0
[ ! -f "$transcript" ] && exit 0

COUNTER="${TMPDIR:-/tmp}/cc-toolleak-${session}.count"

# Last assistant message only
last=$(jq -s 'map(select(.type=="assistant")) | last' "$transcript" 2>/dev/null)
{ [ -z "$last" ] || [ "$last" = "null" ]; } && exit 0

# Pull text content, then strip code fences + inline backticks so that *explaining*
# tool-call syntax doesn't trip the detector. Note: we do NOT skip when a real
# tool_use is present — in multi-tool turns sometimes only *some* calls leak.
text=$(printf '%s' "$last" | jq -r '.message.content[]? | select(.type=="text") | .text' 2>/dev/null)
clean=$(printf '%s' "$text" | awk 'BEGIN{f=0} /^```/{f=!f; next} f==0{print}')
clean=$(printf '%s' "$clean" | sed 's/`[^`]*`//g')

# Leak signature: <invoke name="..."> AND (<parameter name="..."> OR </invoke>)
if printf '%s' "$clean" | grep -qE '<invoke name="[^"]+"' && printf '%s' "$clean" | grep -qE '<parameter name="[^"]+"|</invoke>'; then
  count=$(cat "$COUNTER" 2>/dev/null || echo 0)
  case "$count" in ''|*[!0-9]*) count=0 ;; esac

  # After MAX_RETRY, give up (keep the counter so we stop blocking this episode -> no infinite block).
  if [ "$count" -ge "$MAX_RETRY" ]; then
    exit 0
  fi

  attempt=$((count + 1))
  echo "$attempt" > "$COUNTER"
  cat <<JSON
{"decision":"block","reason":"Your previous response leaked a tool call as plain text without the antml: namespace prefix, so it did not execute (e.g. call <invoke name=...>). Retry ${attempt}/${MAX_RETRY} — re-issue the intended tool call now using the correct antml:invoke / antml:parameter form. Put the tool call at the very start of your response and write any explanation only after you receive the tool result. If it still leaks after ${MAX_RETRY} retries it will keep failing the same way, so start a new session."}
JSON
  exit 0
fi

# Not a leak (normal text turn) -> reset counter + skip
rm -f "$COUNTER"
exit 0

Add it to the Stop array in ~/.claude/settings.json:

{ "hooks": [ { "type": "command", "command": "bash ~/.claude/hooks/stop-hook-toolcall-leak-retry.sh", "timeout": 10 } ] }

This is a stopgap, not a fix — remove it once the regression is resolved. Hope it helps the Windows folks here.

rrrryu3a · 2 months ago

Also reproducing this on Claude Code VS Code extension 2.1.197 (Windows 11), same stray-token pattern (observed both "course" and "court") appearing immediately before <invoke> and rendered as literal text instead of executing.

Two additional data points that differ from the pattern described above:

  • Reproduces in short-context sessions too, not just late in long, tool-call-heavy sessions — happened within the first several turns of a fresh session.
  • Reproduces across model switches (Opus 4.8 and Sonnet 5 in the same session) — not tied to one specific model.

Both points are consistent with #66247's finding that this is a client-side tool_use streaming/parsing regression rather than a context-length or model-specific issue.

BenCizion · 1 month ago

Adding a data point that may help narrow down reproduction conditions.

Environment: macOS, Cowork/Claude Code CLI sessions (mix of Sonnet/Opus).

Pattern observed across multiple long sessions over ~2 weeks: the stray token before <invoke> was consistently one of a small set of short words/phrases — call, course, court, or a Korean prefix word (e.g. "여기"/"이제"/"다음") — placed immediately before the tool-call block in the assistant's own narration. Once it happened once in a session, it tended to recur multiple times in that same session (observed 3–10+ recurrences in a single session on different occasions), suggesting some form of local pattern reinforcement rather than a one-off sampling fluke.

Worst observed case — runaway repetition: in one session, once the model started emitting court as the stray prefix, it did not self-correct or stop. Left unattended, it repeated the same court + leaked <invoke> pattern hundreds of times in a row in a single response, burning a large number of tokens with zero successful tool executions, until the user manually interrupted it. This suggests that once the model enters this failure mode, it can get stuck in a self-reinforcing loop rather than recovering after one or two occurrences — the loop itself seems to need external interruption to break.

**What did not help:** adding explicit system/CLAUDE.md-level instructions telling the model never to type a prefix word before a tool call, and to end narration with a period before emitting the tool-call block. This was tried repeatedly across sessions and the issue still recurred — consistent with this being a harness/parsing-layer issue rather than something promptable away from the model side.

What did help: simply retrying — re-emitting the tool call — after noticing the block leaked as plain text with no corresponding tool_result. No data loss observed, just wasted turns/tokens.

No root cause insight to add beyond what's already here, but wanted to confirm this reproduces consistently across environments/models and that prompt-level mitigation doesn't fix it, in case that's useful for isolating whether it's a streaming/parsing issue in the harness.

Showing cached comments. Read the full discussion on GitHub ↗