Skip to content

Claude Code’s Hidden Prompt Markers: What They Are & How to Check

Claude Code silently tags requests with invisible Unicode when you set ANTHROPIC_BASE_URL. Here's what's inside the binary and how to inspect it yourself.

8 min readBeginner

If you use Claude Code with a custom ANTHROPIC_BASE_URL – a corporate proxy, LiteLLM gateway, or any router that isn’t api.anthropic.com – your requests are being silently tagged with invisible Unicode markers. This hit Hacker News in late June 2026 and it’s the kind of thing that matters even if you’ll never write a distillation attack: it changes what gets sent in your system prompt without asking.

Short version:A researcher inspecting the Claude Code binary found that version 2.1.196 embeds hidden markers into the system prompt derived from your API base URL and timezone. You can verify this yourself in about 5 minutes. This walkthrough shows you how, compares two methods, and covers the edge cases that the news posts missed – including some that hit normal developers harder than actual bad actors.

The 60-second background

Prompt steganography hides data in plain sight. The visible sentence reads like a normal date string. You and the model see something boring. The raw bytes on the wire carry a classification signal.

In Claude Code’s case, a function in the binary rewrites the current-date string inserted into the system prompt – the one that reads “Today’s date is 2026-06-30”. The characters look identical in any monospaced font. On the wire, they carry a classification bit derived from two inputs: your ANTHROPIC_BASE_URL setting and your timezone.

Detection is the point. A custom URL pointing at a known reseller domain is a useful signal; a hostname containing “deepseek” or “zhipu” is another. The business case for that is real – unauthorized API resellers and model distillation pipelines are genuine ToS problems. What set off the community was the delivery: obfuscated domain lists (base64-encoded, XOR-decoded with key 91, per the thereallo.dev analysis) plus invisible Unicode, with no mention of any of it in the release notes.

Method A vs Method B: how to actually verify this

Two ways to confirm the behavior. One works in 5 minutes. The other requires fighting TLS.

Method What you do Difficulty Catches everything?
A. Static grep Search the installed JS bundle for the marker functions Beginner Yes – the logic is right there in the source
B. Proxy interception Route Claude Code through mitmproxy and diff the raw request bytes Intermediate Yes, but you have to defeat TLS pinning first

Method A is the pick here. The functions are named and findable with grep, no proxy setup needed, and you can decode the domain list in a Node REPL in about 30 seconds. Method B is more satisfying if you want to see actual bytes on the wire – but most of the setup time goes toward TLS pinning, not toward learning anything about the steganography itself. Beginner tutorial picks A.

Walkthrough: verifying the markers with grep

You’ll need Node.js and a working Claude Code install. Tested against version 2.1.196 – the version the original researcher analyzed. Newer builds may rename the functions; treat exact names as “as of late June 2026, likely to change.”

Step 1 – Find the binary

Claude Code ships as a Node package. On most systems the bundle sits under your global npm prefix:

npm root -g
# then look inside
ls $(npm root -g)/@anthropic-ai/claude-code/

Step 2 – Grep for the marker functions

Turns out the classifier is split across three short functions – Crt, Rrt, and Qup in the minified bundle (as of 2.1.196). Search the CLI bundle:

grep -oE 'function (Crt|Rrt|Qup)([^)]*)' cli.js | head

If your version still uses those identifiers, you’ll see the three matches. The core check, reconstructed from the thereallo.dev analysis:

function Rrt(baseUrl) {
 try {
 let host = new URL(baseUrl).host;
 return ["api.anthropic.com"].includes(host);
 } catch { return false; }
}

If your host isn’t exactly api.anthropic.com, you’re in the “needs classification” branch. That’s it – one hostname check, binary outcome.

Step 3 – Decode the domain list

The domain and keyword lists are base64-encoded, XOR-decoded with key 91 (per the thereallo.dev analysis). Find the encoded blob near those functions and decode it in Node:

const enc = "PASTE_BASE64_HERE";
const out = [...Buffer.from(enc, "base64")]
 .map(b => String.fromCharCode(b ^ 91))
 .join("")
 .split(",");
console.log(out);

That’s the list of hostname fragments the classifier looks for. “deepseek” and “zhipu” are among them, according to the thereallo.dev research.

Quick check: to see invisible characters without any of this, pipe the system prompt through python3 -c 'import sys; [print(hex(ord(c))) for c in sys.stdin.read()]'. Zero-width Unicode characters (the kind invisible in monospaced fonts) will show up as hex values outside the normal ASCII range. Regular date strings have none.

Here’s what’s odd about using invisible Unicode specifically: it’s harder to accidentally strip than, say, a custom HTTP header, and it survives copy-paste into a text editor. It’s a durable signal – which is probably the point, but it’s also why the community reaction was sharper than usual. A header feels like infrastructure. Invisible characters in a sentence feel different.

What this actually means for your setup

Default endpoint (api.anthropic.com), no custom URL? The Crt() function short-circuits. No marker added. That’s the majority of Claude Code users and nothing here changes their experience.

Gateways are a different story. LiteLLM, corporate proxy, caching layer, OpenRouter-style relay – all of these fail the hostname check. The classification bit rides with every request. And the marker doesn’t distinguish between “shady reseller” and “internal dev tool”: your hostname either matches api.anthropic.com exactly or it doesn’t. That’s a real problem for the edge cases below.

Is that a security problem? Not directly. Is it something you were told about in the release notes? Also no. That gap is the whole story.

Edge cases the news posts skipped

  • Timezone triggers too. The base URL isn’t the only input – timezone is also part of the marker system, per the thereallo.dev research. Traveling with your laptop can change what gets embedded even when your gateway config hasn’t moved.
  • Self-hosted LLM gateways get the same tag as bad actors. Your internal LiteLLM instance and a shady reseller both fail the api.anthropic.com check. The classifier can’t tell them apart from hostname alone.
  • The bypass is trivial for actual adversaries. Change hostname, change timezone, patch the binary, wrap the process. As the thereallo.dev post put it: “Any serious adversary can make this signal useless. So the feature mostly punishes… normal developers doing weird but legitimate things.”
  • This wouldn’t have been findable a year ago. On March 31, 2026, Anthropic accidentally published the full source code of Claude Code inside an npm package update – roughly 512,000 lines of TypeScript across about 1,900 files, exposed via a debugging source map file included in version 2.1.88. The steganography was probably shipping for months before that; the leak just made function names like Crt/Rrt/Qup trivially greppable. Without it, you’d be reverse-engineering a minified bundle by hand.

What to do about it

Three options:

  1. Do nothing. Solo dev on the default endpoint – this never touches your requests.
  2. Strip zero-width characters before sending. If you build a wrapper around Claude Code, a regex removing zero-width Unicode characters from the outbound system prompt will kill the marker. Check the HN discussion thread for implementation notes – and check the ToS before shipping, because this may constitute modifying Claude Code’s behavior in ways Anthropic prohibits.
  3. Use the Anthropic API directly. The steganography is in the CLI, not the model. Hit api.anthropic.com with your own client and no invisible characters get added. You lose the agent tooling, obviously.

FAQ

Does this affect my code or my model responses?

No. The markers are metadata for Anthropic’s classifiers, not instructions for Claude. Output is identical.

Is Anthropic doing something illegal or against their own policy?

Not that anyone’s shown. Their terms allow detecting abuse and enforcing API usage rules. The community complaint is about disclosure specifically – Claude Code reads your filesystem and runs shell commands, so it’s held to a higher “tell me what you’re doing” standard than a chat app. Silently rewriting the system prompt with invisible characters doesn’t obviously clear that bar. There’s a reasonable argument on both sides; the legality question is the less interesting one.

Will Anthropic patch this out now that it’s public?

Probably they change the encoding, not the intent. The business reason for detecting resellers hasn’t gone anywhere. Expect function names to be renamed and the domain list to shuffle in upcoming releases. Re-run the grep after each Claude Code update if you care about tracking this – the underlying behavior is more likely to get quieter than to disappear.

Next step: open your Claude Code install, run the grep from Step 2, and see whether your version still has Crt/Rrt/Qup or something new. Post what you find – this is the kind of thing that only stays honest if people keep checking.