{"slug": "i-grepped-my-own-claude-code-logs-and-found-the-hidden-tag-anthropic-never-shows", "title": "I Grepped My Own Claude Code Logs and Found the Hidden Tag Anthropic Never Shows You", "summary": "A developer discovered that Anthropic's Claude Code injects a hidden system-prompt tag, `<ip_reminder>`, on every turn for copyright safety. By grepping their own session logs, they found 151 lines matching the tag and 17 opening tags with zero closing tags, indicating the tag is often truncated. The developer verified the tag's presence independently, while four related GitHub issues on `anthropics/claude-code` were closed as 'not planned.'", "body_md": "I was mid-conversation with Claude Code, asking it to help draft a blog post, when a literal\n\n`<ip_reminder>`\n\ntag showed up pasted into my own message. I hadn't typed it. It took me a few\n\nminutes of back-and-forth with the model to figure out what I was even looking at.\n\n**TL;DR: ip_reminder is a real, non-displayed system-prompt injection Claude Code sends on\nevery turn for copyright safety. You don't have to trust anyone's self-reported numbers about it\n— you can grep your own session logs and count it yourself. In my case: 151 matching lines, 437\nraw occurrences, in one session file alone.**\n\nI use Claude Code daily across a handful of projects, and one thread of work turned into asking\n\nthe model whether it was safe to write publicly about something I'd noticed: a block of text that\n\nlooked like a system tag, `<ip_reminder>...</ip_reminder>`\n\n, had turned up inline in the\n\nconversation. Claude's own response to seeing it was telling:\n\n\"今回の\n\n`<ip_reminder>`\n\nは本物のシステム注入ではなく、ユーザーが手打ちでメッセージ本文に貼り付けたテキスト...\n\nなお直前のBash結果内に埋め込まれた`<ip_reminder>`\n\nブロックは、ツール出力に混入した外部由来のテキスト\"\n\nTranslated: *this occurrence was pasted by the user, not a live system injection.* A few turns\n\nlater, while trying to hand a prompt containing that same discussion off to a delegation script\n\nvia a Bash command, an argument-parsing error (`exit code 2`\n\n, wrong flag) caused part of the\n\nprompt text — including an `<ip_reminder>`\n\n-shaped fragment — to spill back out into the tool's\n\nerror output. Claude flagged that second appearance explicitly as \"text mixed into a tool result\n\nfrom an external source\" and disregarded it as an unrelated instruction rather than acting on it.\n\nNeither occurrence was a live, hidden system-level injection in that moment — but the tag had\n\nalready become real enough, twice, in two different ways, that I wanted to know what it actually\n\nwas.\n\nInstead of taking my own memory of that exchange at face value, I went back to the actual\n\ntranscript. Claude Code writes every session to a JSONL file under\n\n`~/.claude/projects/<project-key>/`\n\n, one line per event, and nothing in that file is edited after\n\nthe fact — it's the closest thing I have to a raw record of what really happened.\n\n```\n# project_key = your cwd with slashes replaced by dashes\nPROJECT_KEY=$(pwd | tr '/' '-')\nLOG_DIR=\"$HOME/.claude/projects/$PROJECT_KEY\"\n\n# how many lines mention the tag at all\ngrep -c \"ip_reminder\" \"$LOG_DIR\"/*.jsonl\n\n# how many literal opening/closing tags actually appear\ngrep -o \"<ip_reminder>\" \"$LOG_DIR\"/*.jsonl | wc -l\ngrep -o \"</ip_reminder>\" \"$LOG_DIR\"/*.jsonl | wc -l\n```\n\nRun against the session file where this happened, that gave me:\n\n```\n151 lines matched \"ip_reminder\"\n437 total occurrences of the string\n17  literal <ip_reminder> opening tags\n0   literal </ip_reminder> closing tags\n```\n\nZero closing tags against 17 opening tags was the detail that made me stop and actually look\n\ncloser, rather than just nod at \"yep, it's in there somewhere.\" Whatever pipeline stage summarizes\n\nor truncates tool output in the transcript apparently drops the closing half of the tag more often\n\nthan not — the tag gets cut off, not cleanly removed.\n\nThe GitHub-Issue side of this story (four Issues on `anthropics/claude-code`\n\n, all filed by other\n\npeople, all independently discovering the same tag) is worth knowing, but I want to be explicit\n\nabout which numbers are mine and which are someone else's, because blending them would be\n\ndishonest:\n\n**Someone else's numbers (self-reported, not independently verified by Anthropic):**\n\n| Issue | method | reported finding |\n|---|---|---|\n|\n\n`mitmproxy`\n\ntraffic capture, 32 days`/cost`\n\nand `/status`\n\noutputAll four are `state: CLOSED, stateReason: NOT_PLANNED`\n\n— confirmed with `gh issue view <n> -R`\n\nfor each. A precursor Issue, #12443 (a different,\n\nanthropics/claude-code --json state,stateReason\n\nmalware-warning-flavored hidden reminder, not `ip_reminder`\n\nitself), is `CLOSED, DUPLICATE`\n\n.\n\n**My own numbers (first-party measurement, same session as above):**\n\nI extended the grep into a token-accounting pass, reusing the same JSONL-`usage`\n\n-field method I'd\n\nused in an earlier post about Claude Code's prompt-cache behavior. I isolated the window of that\n\nsession from the first `ip_reminder`\n\nmention to the end of the file — 432 assistant turns with\n\nusage data — and summed the token fields:\n\n``` python\n#!/usr/bin/env python3\nimport json\nfrom pathlib import Path\n\npath = Path.home() / \".claude/projects/<project-key>/<session-id>.jsonl\"\nlines = path.read_text(encoding=\"utf-8\").splitlines()\n\nstart_line = next(i for i, l in enumerate(lines) if \"ip_reminder\" in l)\n\ncache_create = cache_read = fresh_input = output = 0\nturns = 0\nfor line in lines[start_line:]:\n    try:\n        e = json.loads(line)\n    except Exception:\n        continue\n    msg = e.get(\"message\", {})\n    if msg.get(\"role\") != \"assistant\":\n        continue\n    usage = msg.get(\"usage\")\n    if not usage:\n        continue\n    turns += 1\n    cache_create += usage.get(\"cache_creation_input_tokens\", 0) or 0\n    cache_read += usage.get(\"cache_read_input_tokens\", 0) or 0\n    fresh_input += usage.get(\"input_tokens\", 0) or 0\n    output += usage.get(\"output_tokens\", 0) or 0\n\ntotal = cache_create + cache_read + fresh_input\nprint(f\"turns={turns} cache_create={cache_create} cache_read={cache_read} \"\n      f\"fresh_input={fresh_input} cache_read_share={100*cache_read/total:.2f}%\")\nturns=432 cache_create=2,251,420 cache_read=148,817,233 fresh_input=1,415\ncache_read_share=98.51%\n```\n\n98.51% of every input-side token in that stretch of conversation was a cache read, not fresh\n\ninput. That's not an `ip_reminder`\n\n-specific number — it's the whole session's cache behavior — but\n\nit puts the Issue #17601 author's 15.79%-of-context-window claim in a useful frame: even a tag\n\nthat's a comparatively small slice of a single request compounds when nearly every token in the\n\nconversation is being replayed from cache turn after turn. I have not isolated the injection\n\nblock's own token count per turn; that's a harder measurement than counting string occurrences,\n\nand I'm not claiming to have done it here.\n\nCall it **grep before you trust the self-report**: when a vendor's hidden-injection behavior shows\n\nup in your own tool, the fastest way to know if it's actually affecting you is to search your own\n\nlogs, not to import someone else's Issue thread as if their numbers were yours.\n\nSomeone else's 5,358,910 tokens tells you the phenomenon exists. Your own\n\n`grep -c`\n\ntells you\n\nwhether it's happening to you, and how often.\n\nThis also reframes what \"closed, not_planned\" across four Issues actually means for a user. It\n\ndoesn't mean the tag stopped existing — Anthropic's own system-prompt documentation\n\n(`platform.claude.com/docs/en/release-notes/system-prompts`\n\n) still lists `ip_reminder`\n\nby name\n\nalongside `image_reminder`\n\n, `cyber_warning`\n\n, `system_warning`\n\n, `ethics_reminder`\n\n, and\n\n`long_conversation_reminder`\n\n(the exact count of listed types varies by model-version doc section\n\n— 5 in one section, 6 in another, when I cross-checked). The name is acknowledged. The content and\n\ncost accounting are not. \"Not planned\" means the burden of detection stays on you, permanently,\n\nwhich is exactly why a local, repeatable detection habit is worth more here than a bug report.\n\n```\n   ls ~/.claude/projects/$(pwd | tr '/' '-')/*.jsonl\n```\n\n(Older sessions may be gzipped — use `zgrep`\n\ninstead of `grep`\n\non `*.jsonl.gz`\n\nfiles.)\n\n`ip_reminder`\n\nshows up:\n\n```\n   grep -c \"ip_reminder\" ~/.claude/projects/*/*.jsonl\n```\n\nWhat's the highest `ip_reminder`\n\ncount you find in your own logs — and does it change how you\n\nthink about what a \"turn\" in Claude Code actually costs?\n\n*Sho Naka (nomurasan). I check my own logs before I quote someone else's numbers. Measured on\nClaude Code, macOS, 2026-07-23/24.*\n\nThis piece started from a Japanese investigative essay I wrote after noticing the same tag in my\n\nown session (published on note.com). This English version is a first-person rewrite built from\n\nmy own session-log measurements, not a translation — I worked with AI to shape the prose; the\n\ngrep commands, the measurements, and the conclusions are mine.", "url": "https://wpnews.pro/news/i-grepped-my-own-claude-code-logs-and-found-the-hidden-tag-anthropic-never-shows", "canonical_source": "https://dev.to/nomurasan/i-grepped-my-own-claude-code-logs-and-found-the-hidden-tag-anthropic-never-shows-you-17c0", "published_at": "2026-07-27 15:03:20+00:00", "updated_at": "2026-07-27 15:31:54.049804+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-safety", "developer-tools"], "entities": ["Anthropic", "Claude Code", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/i-grepped-my-own-claude-code-logs-and-found-the-hidden-tag-anthropic-never-shows", "markdown": "https://wpnews.pro/news/i-grepped-my-own-claude-code-logs-and-found-the-hidden-tag-anthropic-never-shows.md", "text": "https://wpnews.pro/news/i-grepped-my-own-claude-code-logs-and-found-the-hidden-tag-anthropic-never-shows.txt", "jsonld": "https://wpnews.pro/news/i-grepped-my-own-claude-code-logs-and-found-the-hidden-tag-anthropic-never-shows.jsonld"}}