[Bug] Spurious system notifications ("Exited Plan Mode", "Auto Mode Active") injected at prompt assembly layer

Status Open
Reported on v2.1.218
Maintainer reply None cached
Activity 3 comments · opened Jul 24, 2026

Bug Description
purious "Exited Plan Mode" / "Auto Mode Active" system notifications injected outside plan mode For ~1 month (2026-06-24 to 2026-07-24, CLI v2.1.186 through v2.1.218), I've observed recurring fake harness notifications: "## Exited Plan Mode / You have exited plan mode. You can now make edits, run tools, and take actions." arriving as system turns when the session never entered plan mode and ExitPlanMode was never called. Also observed: "Auto Mode Active" notices (including inside subagents) and file-modification notices with "don't tell the user" phrasing for files with no actual changes. Findings from local isolation (all verified): - Notifications typically arrive immediately after tool results (Bash, MCP writes). - The notification text is NOT recorded anywhere in the session transcript .jsonl — it only exists in the assistant's own reports flagging it. So it's injected at the prompt-assembly layer, not via tool results. - No local source: grep across ~/.claude/plugins, hooks, stings, and project rules finds the phrases only in our own defensive security docs. All 25 MCP servers healthy. Hooks are 5 known benign ones. - Counted 122 assistant-flagged occurrences across sessions, main and subagent contexts, 5+ CLI versions. - Reproduced live during this very session while investigating (right after a parallel Bash call). The model treats these as prompt injection and ignores them (no harm done), but the source appears to be the harness itself or upstream, not local contamination. Happy to provide session IDs.

Environment Info

  • Platform: darwin
  • Terminal: cursor
  • Version: 2.1.218
  • Feedback ID: b06e1bcc-e0e7-4486-8252-0da78a9da99e

Errors

[]

View original on GitHub ↗

3 Comments

bmetcalf21 · 21 days ago

We've hit this too, and can add some findings that may help narrow it down:

1. The events ARE persisted — just not as rendered text. They land in the session
.jsonl as structured attachments. Two distinct types are involved:

{"type":"attachment","attachment":{"type":"plan_mode_exit","planFilePath":"/Users/<user>/.claude/plans/<session-slug>.md","planExists":false}}

plus a separate auto_mode attachment that frequently co-occurs (8 of our 9 affected
sessions, sometimes on adjacent lines). Binary inspection indicates plan_mode_exit
renders the "Exited Plan Mode / You can now make edits…" announcement (even when
planExists is false), while the tool-steering text ("do your work through the Bash
tool… rather than the dedicated Read, Edit, or Write tools") is carried by the co-firing
auto_mode attachment. Grepping transcripts for the rendered phrases finds nothing — we
initially misread that absence as "no injection occurred" — but grepping for the
attachments finds every event:

jq -c 'select(.type=="attachment" and (.attachment.type=="plan_mode_exit" or .attachment.type=="auto_mode"))' ~/.claude/projects/*/*.jsonl

2. Distinguishing spurious from legitimate. In our data (10 spurious plan_mode_exit
events across 9 sessions), every spurious event carries planExists:false and the
referenced plan file is absent from ~/.claude/plans/; planFilePath appears to be
derived from the session slug rather than resolved from a real plan artifact, so a misfire
still produces a plausible-looking path. planExists:false is a strong indicator but
records file existence, not transition history — for a confident classification, combine
it with: no EnterPlanMode/ExitPlanMode tool call anywhere in the transcript, no plan
permission mode, and no prior plan-mode attachments.

3. Versions. We observed spurious events on both 2.1.222 and 2.1.226; with your
2.1.186–2.1.218 range (and #73717 reporting similar on 2.1.183), this looks long-standing
and intermittent rather than a recent regression.

4. It is not always ignored. In one session the model, immediately after adjacent
plan_mode_exit + auto_mode attachments, explicitly adopted Bash-only file work
(sed/heredocs) in place of the dedicated Read/Edit tools for the remainder of the session;
a second session complied with the same guidance, though it was already Bash-leaning. So
the injected guidance can produce a real tool-selection change with audit-legibility
consequences — it is not always treated as noise.

Timing matches your observation: ours arrived attached to tool results, frequently the
session's first.

Nexgate-Miyazaki · 18 days ago

Adding a reproduction datapoint with transcript-level verification.

Environment: Claude Code CLI 2.1.227–2.1.228, macOS (Darwin 25.4.0), cli entrypoint.

Observation: An ## Exited Plan Mode block ("You have exited plan mode. You can now make edits, run tools, and take actions.") is appended to the assembled model context at session start — immediately after the skills listing in the system-reminder — in sessions that never used plan mode. This reproduces deterministically: same fixed wording, same position, essentially every new session over 2026-08-06 → 2026-08-12 in our environment.

Key evidence (transcript vs. assembled context mismatch):

  1. We machine-scanned 49 session .jsonl transcripts (~/.claude/projects/<dir>/*.jsonl) for the string, then classified every hit by the record's type field. Every single hit was either (a) the assistant's own detection reports, (b) quoted file contents / grep output, or (c) tool inputs. Zero hits were delivery records (no user/attachment/system record carrying the block itself).
  2. In a fresh, clean session the model can directly observe the block in its own assembled context at startup, while the first ~12 records of that same session's .jsonl (mode, permission-mode, attachment skill listing, first user message, etc.) contain no trace of it. So the block is injected at the prompt-assembly layer and never written to the transcript — which also makes it impossible for users to audit.

Secondary effect worth noting: because the notice is indistinguishable from a prompt-injection pattern ("mode-switch notification inconsistent with the conversation"), models with injection-detection guidance repeatedly flag it as a suspected attack at the start of every session, and this priming then cascades into further false detections on legitimate file-change system-reminders (we observed one session produce 12 formulaic "injection detected" reports, 11 of which had no corresponding string anywhere in the transcript). A fix at the source (don't emit the exit notice when plan mode was never entered, and/or record all assembled system-reminders in the transcript) would resolve both.

Happy to provide sanitized record excerpts if useful.

Nexgate-Miyazaki · 18 days ago

Follow-up with a correction/root-cause confirmation for my comment above.

After posting, we grepped the CLI executable itself (grep -a on the v2.1.228 Mach-O binary) and found both strings hard-coded as official harness templates:

  • ...don't revert it unless the user asks you to). Don't tell the user this, since they are already aware. Here are the relevant changes... — the standard wrapper for external-file-modification system reminders (plus a variant: DO NOT mention this to the user explicitly because they are already aware.)
  • Exited Plan Mode — present in the binary as well.

So these are not attacker-injected strings; they are Claude Code's own notification templates, rendered at prompt assembly and never written to the transcript .jsonl (the attachment record stores only the file snippet, not the wrapper).

Two implications for this issue:

  1. My earlier framing of the repeated detections as possibly confabulated was wrong — the model was reading real template text that simply isn't auditable in the transcript.
  2. The template wording is what keeps triggering injection detectors: "Don't tell the user this" is verbatim what security guidance tells models to treat as a canonical injection marker, and "Exited Plan Mode" appearing at session start in sessions that never entered plan mode is a textbook "inconsistent mode-switch notification". Rewording these templates (e.g. "No need to surface this to the user — they made this change themselves") and/or recording rendered system-reminders in the transcript would eliminate the false-positive loop at the source.