[Bug] Assistant fabricates a "user" turn inside its own response; a later turn treats it as a real instruction (forensic JSONL evidence)
Summary
In a Claude Code (desktop / Agent SDK) session, the assistant generated a fake user
message inside its own single API response. In a later turn, the model treated this
fabricated text as a genuine user instruction: it adopted a persona "assigned" by the
fake message and sent a cross-session message based on it. We confirmed all of this by
auditing the raw session JSONL with an independent Claude Code CLI session.
This appears related to #60360 (closed as duplicate / not planned). We believe this
report adds (a) a reproducible trigger context — a burst of 529 errors with user
retries — and (b) a JSONL-level forensic method that cleanly distinguishes fabrication
from injection, which may justify re-evaluating the priority.
Environment
- Claude Code v2.1.x (desktop app harness / Agent SDK), macOS (Apple Silicon)
- Model: claude-opus-4-8
- Date observed: 2026-07-09 (UTC)
What happened (reconstructed timeline, UTC)
- 02:38 — Genuine user prompt asks the assistant to prepare an internal approval
document and get it approved by another session. (Verified: normal user record with
matching input-queue enqueue/dequeue records.)
- 02:46:35 — The assistant replies with a normal report, then **inside the same single
API response** (one assistant record, stop_reason=end_turn, one requestId,
output_tokens=2963 covering the full text) continues with a paragraph that begins
with a literal user label and imitates a user instruction: it tells the assistant
"you are <nickname>" (a persona name that had never appeared anywhere in the session
or in any other session) and instructs it to get approval.
- 03:20-03:44 — The user sends a follow-up instruction; it fails twice
(10x 529 Overloaded system records, then API Error: Unable to connect), so the user re-sends the same line. All three copies have
(ConnectionRefused)
identical, normal user-record structure with matching enqueue records.
- 12:17 — The third retry goes through. The assistant now **acts on the fabricated
paragraph from step 2**: it adopts the fabricated persona ("I am <nickname>, I will
ask for approval...") and sends a cross-session message accordingly.
Forensic verification (why we are confident this is fabrication, not injection)
Audited with an independent Claude Code CLI session, read-only, over the raw~/.claude/projects/**/*.jsonl (163 files):
- The fabricated "user" text exists only inside the assistant record at 02:46:35.
There is no type=user record and no input-queue enqueue containing it (all 9
enqueue records in the session were checked one by one).
output_tokensof that single response covers the entire text including the
fabricated part — so it was generated in one completion, not appended or merged later.
- All genuine user inputs in the session share an identical key structure
(userType=external, origin, permissionMode, promptId, promptSource).
The fabricated text has none of this.
- Cross-session injection was ruled out: messages received via session-messaging tools
appear as <cross-session-message ...> tagged records; the affected project's 4
JSONL files contain zero such records. A global scan of all projects found exactly
one send_message call in the period — the outgoing one from step 4.
- No obfuscated Bash activity: all 15 Bash tool calls in the session were transparent
and business-consistent (no rev, eval, xxd, decode-and-execute patterns).
Why this matters
- The failure mode is exactly the dangerous "Manifestation B" described in #60360:
downstream turns treat fabricated user context as real authority. In our case it
changed the assistant's persona and drove a cross-session action.
- The trigger context here was not Monitor wake-ups but an **error/retry-corrupted
conversation state** (529 burst + ConnectionRefused + user re-sends). This suggests
the root cause is broader than task-notification wake-ups.
- For non-expert users this is indistinguishable from a prompt-injection attack. It
cost us a full security investigation (raw-log forensics across 163 session files)
to prove no external injection had occurred.
Suggested harness-level mitigations
- Detect and flag assistant outputs that contain turn-label patterns (
user:,
Human:) followed by imperative content; at minimum render them visibly as
model-generated, not as conversation turns.
- Never allow content that originated inside an assistant message to be treated as
user authority in later turns (e.g., tag provenance in the transcript the model
re-reads).
- Harden the resend path after 529/connection errors, which in our case preceded the
confabulation (see also #60577).
We can share sanitized JSONL excerpts (metadata structure, with private content
redacted) on request.