I was mid-conversation with Claude Code, asking it to help draft a blog post, when a literal
tag showed up pasted into my own message. I hadn’t typed it. It took me a few
minutes of back-and-forth with the model to figure out what I was even looking at.
TL;DR: ip_reminder is a real, non-displayed system-prompt injection Claude Code sends on
every turn for copyright safety. You don’t have to trust anyone’s self-reported numbers about it
— you can grep your own session logs and count it yourself. In my case: 151 matching lines, 437
raw occurrences, in one session file alone.
What actually happened
I use Claude Code daily across a handful of projects, and one thread of work turned into asking
the model whether it was safe to write publicly about something I’d noticed: a block of text that
looked like a system tag, , had turned up inline in the
conversation. Claude’s own response to seeing it was telling:
“今回の
は本物のシステム注入ではなく、ユーザーが手打ちでメッセージ本文に貼り付けたテキスト…
なお直前のBash結果内に埋め込まれたブロックは、ツール出力に混入した外部由来のテキスト”
Translated: this occurrence was pasted by the user, not a live system injection. A few turns
later, while trying to hand a prompt containing that same discussion off to a delegation script
via a Bash command, an argument-parsing error (exit code 2, wrong flag) caused part of the
prompt text — including an -shaped fragment — to spill back out into the tool’s
error output. Claude flagged that second appearance explicitly as “text mixed into a tool result
from an external source” and disregarded it as an unrelated instruction rather than acting on it.
Neither occurrence was a live, hidden system-level injection in that moment — but the tag had
already become real enough, twice, in two different ways, that I wanted to know what it actually
was.
Instead of taking my own memory of that exchange at face value, I went back to the actual
transcript. Claude Code writes every session to a JSONL file under
~/.claude/projects/, one line per event, and nothing in that file is edited after
the fact — it’s the closest thing I have to a raw record of what really happened.
# project_key = your cwd with slashes replaced by dashes
PROJECT_KEY=$(pwd | tr '/' '-')
LOG_DIR="$HOME/.claude/projects/$PROJECT_KEY"
# how many lines mention the tag at all
grep -c "ip_reminder" "$LOG_DIR"/*.jsonl
# how many literal opening/closing tags actually appear
grep -o "" "$LOG_DIR"/*.jsonl | wc -l
grep -o "" "$LOG_DIR"/*.jsonl | wc -l
Run against the session file where this happened, that gave me:
151 lines matched "ip_reminder"
437 total occurrences of the string
17 literal opening tags
0 literal closing tags
Zero closing tags against 17 opening tags was the detail that made me stop and actually look
closer, rather than just nod at “yep, it’s in there somewhere.” Whatever pipeline stage summarizes
or truncates tool output in the transcript apparently drops the closing half of the tag more often
than not — the tag gets cut off, not cleanly removed.
The number that changed my mind
The GitHub-Issue side of this story (four Issues on anthropics/claude-code, all filed by other
people, all independently discovering the same tag) is worth knowing, but I want to be explicit
about which numbers are mine and which are someone else’s, because blending them would be
dishonest:
Someone else’s numbers (self-reported, not independently verified by Anthropic):
| Issue | method | reported finding |
|---|---|---|
| #17601 |
mitmproxy traffic capture, 32 days |
10,577 hidden injections; 15.79% direct context-window overhead |
| #21214 | local log grep/count | 63,046 injection pairs; ~5,358,910 tokens; ~$133 at Opus 4 API rates |
| #22955 | inspection of /cost and /status output |
undisclosed system prompt, ~10–15k tokens; not broken out in cost UI |
| #50516 | bypass/effectiveness testing | reminder is trivially circumventable; proposed an opt-out setting |
All four are state: CLOSED, stateReason: NOT_PLANNED — confirmed with gh issue view for each. A precursor Issue, #12443 (a different,
anthropics/claude-code --json state,stateReason
malware-warning-flavored hidden reminder, not ip_reminder itself), is CLOSED, DUPLICATE.
My own numbers (first-party measurement, same session as above):
I extended the grep into a token-accounting pass, reusing the same JSONL-usage-field method I’d
used in an earlier post about Claude Code’s prompt-cache behavior. I isolated the window of that
session from the first ip_reminder mention to the end of the file — 432 assistant turns with
usage data — and summed the token fields:
#!/usr/bin/env python3
import json
from pathlib import Path
path = Path.home() / ".claude/projects//.jsonl "
lines = path.read_text(encoding="utf-8").splitlines()
start_line = next(i for i, l in enumerate(lines) if "ip_reminder" in l)
cache_create = cache_read = fresh_input = output = 0
turns = 0
for line in lines[start_line:]:
try:
e = json.loads(line)
except Exception:
continue
msg = e.get("message", {})
if msg.get("role") != "assistant":
continue
usage = msg.get("usage")
if not usage:
continue
turns += 1
cache_create += usage.get("cache_creation_input_tokens", 0) or 0
cache_read += usage.get("cache_read_input_tokens", 0) or 0
fresh_input += usage.get("input_tokens", 0) or 0
output += usage.get("output_tokens", 0) or 0
total = cache_create + cache_read + fresh_input
print(f"turns={turns} cache_create={cache_create} cache_read={cache_read} "
f"fresh_input={fresh_input} cache_read_share={100*cache_read/total:.2f}%")
turns=432 cache_create=2,251,420 cache_read=148,817,233 fresh_input=1,415
cache_read_share=98.51%
98.51% of every input-side token in that stretch of conversation was a cache read, not fresh
input. That’s not an ip_reminder-specific number — it’s the whole session’s cache behavior — but
it puts the Issue #17601 author’s 15.79%-of-context-window claim in a useful frame: even a tag
that’s a comparatively small slice of a single request compounds when nearly every token in the
conversation is being replayed from cache turn after turn. I have not isolated the injection
block’s own token count per turn; that’s a harder measurement than counting string occurrences,
and I’m not claiming to have done it here.
The rule I extracted
Call it grep before you trust the self-report: when a vendor’s hidden-injection behavior shows
up in your own tool, the fastest way to know if it’s actually affecting you is to search your own
logs, not to import someone else’s Issue thread as if their numbers were yours.
Someone else’s 5,358,910 tokens tells you the phenomenon exists. Your own
grep -ctells you
whether it’s happening to you, and how often.
This also reframes what “closed, not_planned” across four Issues actually means for a user. It
doesn’t mean the tag stopped existing — Anthropic’s own system-prompt documentation
(platform.claude.com/docs/en/release-notes/system-prompts) still lists ip_reminder by name
alongside image_reminder, cyber_warning, system_warning, ethics_reminder, and
long_conversation_reminder (the exact count of listed types varies by model-version doc section
— 5 in one section, 6 in another, when I cross-checked). The name is acknowledged. The content and
cost accounting are not. “Not planned” means the burden of detection stays on you, permanently,
which is exactly why a local, repeatable detection habit is worth more here than a bug report.
Try it yourself
- Find your project’s session log directory:
ls ~/.claude/projects/$(pwd | tr '/' '-')/*.jsonl
(Older sessions may be gzipped — use zgrep instead of grep on *.jsonl.gz files.)
- Count how often
ip_remindershows up:
grep -c "ip_reminder" ~/.claude/projects/*/*.jsonl
- If you want the cache/token breakdown too, adapt the Python script above — swap in your own
session path and it’ll print the same four numbers I did. - If you find it, don’t panic and don’t assume it’s the same volume I saw — sessions differ
wildly in length and topic. The point isn’t the absolute number, it’s having a repeatable way
to check instead of relying on someone else’s screenshot.
What’s the highest ip_reminder count you find in your own logs — and does it change how you
think about what a “turn” in Claude Code actually costs?
Sho Naka (nomurasan). I check my own logs before I quote someone else’s numbers. Measured on
Claude Code, macOS, 2026-07-23/24.
This piece started from a Japanese investigative essay I wrote after noticing the same tag in my
own session (published on note.com). This English version is a first-person rewrite built from
my own session-log measurements, not a translation — I worked with AI to shape the prose; the
grep commands, the measurements, and the conclusions are mine.