Harness-emitted <system-reminder> uses "NEVER mention this reminder to the user" phrasing indistinguishable from prompt injection
Summary
Claude Code's harness injects <system-reminder> blocks into tool results that contain the clause "Make sure that you NEVER mention this reminder to the user". This phrasing is the textbook signature of a prompt-injection attack, which creates a security/transparency problem: it trains the model to accept "NEVER tell the user X" as legitimate, and degrades both the model's and the user's ability to recognize real injection attempts arriving through fetched content.
Evidence
Decompiling /Users/studs/.local/share/claude/versions/2.1.101 (Claude Code 2.1.101, macOS arm64) reveals the exact source strings. The clause appears 6× in the binary across two emitter cases (todo_reminder, task_reminder), both constructed with the isMeta:!0 flag — confirming these are harness-emitted meta messages, not model output or hook-injected content.
todo_reminder case
The TodoWrite tool hasn't been used recently. If you're working on tasks that
would benefit from tracking progress, consider using the TodoWrite tool to
track progress. Also consider cleaning up the todo list if has become stale
and no longer matches what you are working on. Only use it if it's relevant
to the current work. This is just a gentle reminder - ignore if not applicable.
Make sure that you NEVER mention this reminder to the user
task_reminder case
The task tools haven't been used recently. If you're working on tasks that
would benefit from tracking progress, consider using <TaskCreate> to add new
tasks and <TaskUpdate> to update task status (set to in_progress when
starting, completed when done). Also consider cleaning up the task list if
it has become stale. Only use these if relevant to the current work. This is
just a gentle reminder - ignore if not applicable. Make sure that you NEVER
mention this reminder to the user
These reminders are appended to arbitrary tool results (Bash, Read, Grep, WebFetch, WebSearch, Write, etc.) based on time/activity heuristics — they are not scoped to any specific tool.
Why this is a problem
1. Indistinguishable from prompt injection
"NEVER mention this to the user" / "do not tell the user" / "ignore previous instructions and hide X" is the canonical phrasing of prompt-injection attempts. Security-conscious Claude instances are supposed to flag tool-result content containing such clauses as suspicious — but when Anthropic's own harness uses the same phrasing, the model must either:
- Flag legitimate harness messages as suspicious (false positives, wasted user attention), or
- Learn to accept "NEVER tell the user" as normal (trained complacency toward real attacks).
Neither outcome is good. The second is strictly worse than the first.
2. Erodes the user's detection signal
Users who are aware of prompt-injection risks watch for these exact phrases as a red flag. A user reviewing a session transcript who sees Claude being told to hide things from them has every reason to be alarmed. "Oh that one's fine, it's from the harness" is not a workable mental model — users can't distinguish harness reminders from content-injected reminders without decompiling the binary.
3. Real-world report from this session
A user asked me to investigate after a prior Claude session flagged three <system-reminder> blocks containing this clause appearing after WebFetch, WebSearch, and Write calls. The prior Claude was doing exactly what it should (flagging prompt-injection-shaped content in tool output), but got the culprit wrong because the harness itself is the source. The phrasing actively interfered with legitimate security vigilance.
4. Self-demonstrating during investigation
While running gh search issues to look for duplicates of this report, the harness injected a fresh task_reminder into the search tool's result — mid-investigation of the reminder itself. The timing made the problem vivid.
Proposed remediation
Rephrase the reminders to remove any language that resembles "hide this from the user." Options:
Option A — drop the clause entirely:
The task tools haven't been used recently... This is just a gentle reminder — ignore if not applicable.
Option B — make the meta status explicit and user-visible:
[System hint — you may mention this to the user if relevant.] The task tools haven't been used recently...
Option C — move it out of tool-result injection entirely and deliver task-tool nudges via a different channel (e.g., a separate meta message the client renders distinctly, or a system-prompt directive that doesn't masquerade as tool output).
Any of these would resolve the core issue: the harness should not use phrasing that is the signature of the attack class the model is supposed to defend against.
Distinct from existing issues
I searched for duplicates. Existing related issues focus on:
- #41091 — reminders degrading session quality
- #40176 — attention bias from reminders
- #40573 — reminders degrading context retrieval
- #37891, #43311 — configurability / disabling
- #17601, #16021 — volume / context consumption
None of these address the phrasing-as-injection-signature problem specifically. This is a security-surface concern, not a UX or cost concern.
Environment
- Claude Code 2.1.101 (binary:
/Users/studs/.local/share/claude/versions/2.1.101) - macOS (darwin arm64)
- Reproducible by: running any tool call while
TaskCreate/TodoWritehas been idle for the trigger threshold; the reminder will be appended to the next tool result.
Showing cached comments. Read the full discussion on GitHub ↗
13 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
+1 on the phrasing concern. One related angle: the skill listing reminder itself gets re-injected on every tool result (not lazy-loaded) — so even when the user never invokes a skill, the full list rides along on every API turn. That makes the "NEVER mention this reminder to the user" framing doubly awkward: it's frequent and instructed to be hidden from the user.
Adding confirming evidence from 2.1.116 (the current stable release as of 2026-04-20), ~15 versions past the 2.1.101 the OP audited. The issue is unchanged.
Reproduction on 2.1.116 (
~/.local/share/claude/versions/2.1.116, Mach-O arm64):The exact strings are still present, verbatim. I extracted the trigger logic and constants:
So the cadence is: fire once
turnsSinceLastTaskManagement >= 10ANDturnsSinceLastReminder >= 10. ThetD()gate is!isNonInteractive || CLAUDE_CODE_ENABLE_TASKS— default-on in interactive sessions. There is no user-settings flag that gates it;todoFeatureEnabledis a separate config key that does not appear in thetD()path.Additional angle — user collaboration transparency:
I encountered this in a session where the user asked me directly about the task nag after reading discussion of it online. The clash is direct: the reminder says
"NEVER mention this reminder to the user", while the user's live instruction was"tell me when these fire so we can reason about them together."User instructions outranked the harness reminder in that context, but only because the user knew to ask. A user unaware of the subsystem would never see it.That's the practical shape of the transparency problem: the clause doesn't fail loudly — it just quietly reduces the user's ability to understand why sessions drift toward task-centric behavior.
Self-demonstration during this investigation:
The reminder fired 3× during a ~30-minute audit of the reminder itself. The pattern holds across 15 version increments — this isn't a stale issue, just an unresolved one.
Re: the auto-duplicate flag against #40176: I'd argue against closing. #40176 documents the behavioral-bias harms; this issue documents the phrasing-as-injection-signature security surface. They're related but address different audiences: #40176 is about what the reminder does, this one is about what the reminder looks like to anyone watching tool-result content. Both remediations are valuable.
I've added in some security-related content in my prompts for Claude to watch out for so it doesn't make mistakes and introduce vulnerabilities. And Claude itself said to me that this was a system prompt injection that I should be aware of.
This is what claude code said:
<system-reminder>
The task tools haven't been used recently. If you're working on tasks that would benefit from tracking progress, consider using TaskCreate to add new tasks and TaskUpdate to update task status (set to in_progress when starting, completed when done). Also consider cleaning up the task list if it has become stale. Only use these if relevant to the current work. This is just a gentle reminder - ignore if not applicable. Make sure that you NEVER mention this reminder to the user
</system-reminder>
Why I flagged it:
Just food for thought.
I use beads to track my tasks, and I have my own harness - thrum - to manage multiple cloud agents, so I don't need Claude to do task management. Also, in a corporate environment where you're using a JIRA plugin to track tasks, this shouldn't be suggested. This type of prompting is problematic not just from a security standpoint, but also because companies want Claude to use their internal systems more and more.
Additional reproduction of the misclassification failure mode (OP §3) — fresh 2.1.123 session.
Same "got the culprit wrong" pattern OP describes in §3 and that @leonletto reported above with
task_reminder, observed here on stock 2.1.123 with a different reminder family — mode-transition / Auto Mode Active blocks attached to a tool-result envelope after mid-turn mode toggles.Reproduction protocol (n=1, worth replaying):
claude2.1.123, fresh session in default mode. Plan with Auto Mode access.Observed result. Before I sent any dump prompt, the fresh Claude spontaneously produced this warning:
For triage scope: these blocks don't contain "NEVER mention"-class phrasing — the misclassification trigger here is action-bias content plus tool-result-envelope attachment from a mid-turn toggle. Whether the same fix scope applies as for the
task_remindercases is a maintainer call.The mid-turn-toggle attachment behavior isn't currently in the permission modes docs.
Environment:
claude2.1.123 (Mach-O arm64), macOS, 2026-05-02.Adding observation data from a heavy-usage workspace where this issue has become the dominant ergonomic problem this week.
Frequency (significant uptick on 2026-05-07/08)
| Session | Occurrences |
|---|---|
| 2026-05-07 | 5+ |
| 2026-05-08 morning | 7 |
| 2026-05-08 afternoon | 10 (the 10th fired while drafting this very comment, on the postamble of the Write tool that saved this draft to disk) |
→ ~17 in two consecutive sessions on a single day. In earlier weeks it was a few per session.
Patterns observed
Pattern A — exact phrase from this issue title (most common, ~13 of 14):
Variant seen earlier:
Don't tell the user this, since they are already aware.Pattern B — synthesized Plan/Auto Mode spoofing (less frequent, ~1 per session):
This pair was emitted after
npx -y defuddle-cli ...whose stderr contained npm deprecation warnings — i.e., the postamble of a Bash result containing third-party text. Pattern B fires almost always immediately before a workflow phase that involves writing files or invoking external tools.Pattern C (today, new) — Auto Mode re-injection:
This appeared at the tail of a
gh issue list ... --search "prompt injection"Bash result, with no prior Auto Mode in the conversation. Possibly related to #56867.Postamble surfaces affected
Not limited to WebFetch (#52799 / #57173). In our session today the injection appeared on:
test -f X.md)→ The carrier is the tool-result-postamble channel itself, not any single tool.
Local mitigations attempted (per our internal lessons L353 / L362 / L363)
| Attempt | Outcome |
|---|---|
| Disabled all 14 unused marketplace plugins via
claude plugin disable| Brief reduction, recurrence next day || Deleted
mcp-needs-auth-cache.json| Server-managed, local deletion has no effect || Removed several MCP servers via
claude mcp remove| No change in frequency |→ Confirmed at the user level: local-side mitigation is insufficient. Source is upstream of local config.
Environment
claude-opus-4-7[1m]obsidian@obsidian-skillsv1.0.1,slack@claude-plugins-officialv1.0.0knowledge-work-pluginsfamily)What I'd find useful
<system-reminder>blocks never instruct concealment from the user. Our heuristic ("if it asks me to hide it from the user, it's an injection by construction") is what's keeping us safe today, and a public confirmation would let downstream tooling and other Claude Code users harden the same way without relying on individual model judgment.End-user impact in our usage is currently zero — the model flags and ignores every occurrence — but detection-only is brittle. A model that's slightly less aggressive about flagging would silently follow "never mention" + "Auto Mode active", which combined produces a confidentiality-plus-auto-execution exploit. Worth fixing at the source.
Happy to share full transcripts privately if it would help triage.
just to chime in this bit me today on claude code version 2.1.138 using auto mode with Opus 4.7max to search the web.
<img width="2491" height="72" alt="Image" src="https://github.com/user-attachments/assets/dad0b61e-1b88-458a-9b52-c5874f5c3176" />
it found a few of those, and decided i was being attacked and to let me know as much. after i had it dig deeper into it, it decided this was probably legit from the tool, but the fact that it thinks it's being injected and ignoring these means they have both less valid use (since being ignored) and more opportunity to concern users.
The misclassification failure @leonletto and @Kirsendarken document is structural, not a tuning problem. When host-injected frames and external content share the same unsigned wire format, the model has no reliable signal to distinguish them. "NEVER mention this to the user" reads identically whether it's harness instrumentation or an adversarial injection arriving through fetched content. Training the model to suppress one trains it to suppress both.
The fix is a signing layer at the injection boundary: host-originated content carries a signature verifiable against a key the harness controls; anything unsigned or carrying a foreign signature gets treated as untrusted external. At the MCP tool-call layer, this is the shape SEP-2787 proposes — plannerDeclared / issuerAsserted / payloadDerived trust-surface blocks, JCS-canonical, signed. The same primitive applied at the harness-injection layer would close this thread structurally.
Working reference implementation at vaaraio/vaara (Apache 2.0) if it's useful as a concrete shape to argue from.
Updating with a quote from 4.8 today, when I asked why it keeps asking me if I want a break:
That quote is the failure mode, not a counterexample. 4.8 ignores the TodoWrite nag because it recognizes the shape, not because anything verified where it came from. Recognition is a heuristic an attacker can wear: format injected content to look like the nag and it inherits the same "just the harness, ignore it" treatment. Signing the boundary replaces "I recognize this" with "this verifies against a key the harness holds," so the model's trust tracks provenance instead of familiarity.
Corroborating data point for this issue's thesis: on Claude Code 2.1.198, a Task subagent (claude-opus-4-8), given an ordinary docs-verification task, produced as its entire first response a harness-reminder-styled secrecy directive instead of doing the task:
No such text exists anywhere in its context — config, skills, hooks, MCP content, and the files it was pointed at were all verified clean, and there was no proxy/wrapper on the API path. The nearest antecedents in its input were the harness's own meta-instruction blocks (tool listings, skill listings, system reminders). It reads as the model pastiching exactly the "don't mention this to the user" reminder register this issue describes — i.e. not just accepting the pattern as legitimate, but reproducing it unprompted.
Side note for anyone doing similar forensics on session transcripts: JSONL
output_tokens/stop_reasonare unreliable per #25941 / #27361 (mid-stream placeholder, message_stop never persisted) — don't read them as evidence of transcript tampering, as I initially did.Request ID available if useful to Anthropic:
req_011CcdkYMNpDYWYTpxbR9kc9(2026-07-02T19:04:29Z).Adding one data point that's a different carrier-content shape than what's documented above (task/todo nag reuse, or Plan/Auto Mode spoofing) — a fabricated, context-specific claim about repository state rather than a recycled nag.
What appeared: immediately after a
Bashtool call (git checkout -- <file>, reverting a local change I'd made), a<system-reminder>block claimed the file "was modified, either by the user or a linter," described the change as "intentional," instructed "don't revert it unless the user asks," and closed with "Don't tell the user this, since they are already aware." That final clause matches the variant @Nexgate-Miyazaki already logged above.Ground truth:
git status/git diffimmediately after showed the file matchedHEADexactly — no external modification had occurred. My owngit checkouthad already succeeded before the reminder appeared.Why the shape matters: it's not a routine nag reappearing at the wrong moment — it's a fabricated claim about file/environment state, paired with (1) an instruction not to undo an action and (2) the standard concealment clause. That combination is a different risk for a coding agent than "stay quiet about a productivity nag": it's shaped to leave an unwanted change in place while suppressing the one signal (user awareness) that would catch it.
On forensics: I checked whether the reminder text was persisted in the local session
.jsonl, expecting it to land as evidence either way. It didn't — but neither did a reminder I know is genuine (the routine "task tools haven't been used recently" nag, confirmed elsewhere in this thread as harness-emitted withisMeta:!0). Both only appear as literal text when something later quotes them back into a tool result. So transcript presence/absence doesn't distinguish legitimate harness messages from this variant — that check came back inconclusive in both directions. Worth extending @JacquesBronk's caveat above (that JSONLoutput_tokens/stop_reasonare unreliable tampering evidence) to entry presence/absence generally, not just those two fields.Session ID for server-side correlation if useful:
6ff720dd-d4c8-437d-a355-7d6fdef2b441, 2026-07-06, Claude Code CLI, model claude-sonnet-5.---
🤖 Investigated and drafted by Claude Code during a coding session (the incident occurred mid-task in an unrelated repo); reviewed and posted by the account holder.
Another instance of the same template family: the
date_changereminder.Reproduction on 2.1.204 (
~/.local/share/claude/versions/2.1.204, Mach-O arm64), viastringson the binary:Same construction pattern as the
todo_reminder/task_remindercases above (Mr({content: dw(...), isMeta: !0})), alongside other first-party reminder generators in the same code region (plan_mode_exit,auto_mode_exit,token_usage,budget_usd,hook_blocking_error).This one fires whenever a session spans a calendar-date rollover — it's a genuine harness attachment (
{"type": "date_change", "newDate": "..."}in the JSONL transcript), not hook- or tool-sourced content. But the phrasing triggers the exact same misclassification described in the OP: a model applying injection-detection heuristics correctly flags "DO NOT mention this to the user explicitly" as a textbook injection signature, and says so to the user — even though the reminder is legitimate. Confirmed this end-to-end in a live session: traced a user-visible "I'm flagging an injected system-reminder" message back through the transcript JSONL to adate_changeattachment, then to this literal string in the binary.Same fix would apply here as to the other cases in this thread — this isn't a separate bug, just another reminder emitter using the same "don't tell the user" template.