I was mid-conversation with Claude Code, asking it to help draft a blog post, when a literal
<ip_reminder>
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.
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, <ip_reminder>...</ip_reminder>
, had turned up inline in the
conversation. Claude's own response to seeing it was telling:
"今回の
<ip_reminder>
は本物のシステム注入ではなく、ユーザーが手打ちでメッセージ本文に貼り付けたテキスト...
なお直前のBash結果内に埋め込まれた<ip_reminder>
ブロックは、ツール出力に混入した外部由来のテキスト"
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 <ip_reminder>
-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/<project-key>/
, 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=$(pwd | tr '/' '-')
LOG_DIR="$HOME/.claude/projects/$PROJECT_KEY"
grep -c "ip_reminder" "$LOG_DIR"/*.jsonl
grep -o "<ip_reminder>" "$LOG_DIR"/*.jsonl | wc -l
grep -o "</ip_reminder>" "$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 <ip_reminder> opening tags
0 literal </ip_reminder> 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 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 |
|---|---|---|
mitmproxy
traffic capture, 32 days/cost
and /status
outputAll four are state: CLOSED, stateReason: NOT_PLANNED
— confirmed with gh issue view <n> -R
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/<project-key>/<session-id>.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.
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 -c
tells 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.
ls ~/.claude/projects/$(pwd | tr '/' '-')/*.jsonl
(Older sessions may be gzipped — use zgrep
instead of grep
on *.jsonl.gz
files.)
ip_reminder
shows up:
grep -c "ip_reminder" ~/.claude/projects/*/*.jsonl
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.