[BUG] Account email (PII) is injected into the model's system prompt without consent, disclosure,
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Claude Code takes personal data I gave Anthropic in order to authenticate, and injects it into the model's system prompt on every session — automatically, without my consent, without disclosing that it does so, and with no supported way to stop it.
That is the whole complaint. Everything below is evidence for it.
The injected block:
# userEmail
The user's email address is <account email>.
From the v2.1.220 bundle, the user-context builder:
let n = process.env.ANTHROPIC_UNIX_SOCKET ? void 0 : dc()?.emailAddress
// ...
return {
...(r && { claudeMd: r }),
...(n && { userEmail: `The user's email address is ${n}.` }),
...(o && { attachedProject: o }),
currentDate: `Today's date is ${LVe()}.`
}
The value is read from ~/.claude.json → oauthAccount.emailAddress.
Without knowledge. This behavior is documented nowhere. It is not in the environment-variables reference, the authentication page, the data-usage page, or the settings page. I found it only by decompiling the shipped bundle, and four other users independently had to do the same (see Additional Information). Authenticating with an account is not notice that the account's address will be handed to a language model.
Without consent. There is no setting, flag, or documented mechanism to prevent it. The only code path that skips the injection is process.env.ANTHROPIC_UNIX_SOCKET, which selects a different transport and is not a privacy control. Compare the adjacent claudeMd field, built on the same object, which does have a disable path (claudemd_disabled): a file I wrote myself is opt-out-able; my email is not.
Nothing uses it. userEmail appears exactly three times in the entire bundle: the construction site above, and two telemetry events (user_context_completed, tengu_context_size) that record has_user_email: Boolean(...) — a presence flag, not the value. No feature reads it. It is not needed for authentication (the OAuth token travels in request headers) or for billing (resolved server-side against the account). I can state that no consumer exists; I cannot state what the intent was, because minification strips comments and the docs are silent.
The one privacy control that exists stops at the screen — and fails its own stated purpose. IS_DEMO=1 is documented as hiding "your email and organization name from the header and /status output," and is recommended for "streaming or recording a session." In the bundle, all seven IS_DEMO gates guard display surfaces only: the /status Email and Organization rows (two code paths), the startup banner's org name, the "Message from <org>:" notice, and onboarding. Not one touches the context builder.
That makes demo mode ineffective for the exact scenario it advertises. The address is still in the model's context, and the model's replies print to the same terminal being streamed. There is no output-side filtering to catch it: the bundle contains no redactEmail/scrubEmail/maskEmail/sanitizeEmail, no redaction logic near emailAddress, and no IS_DEMO check anywhere near transcript rendering. So a user who enables demo mode specifically to record safely gets a clean header while the model remains free to print the address into the transcript at any moment.
This is not hypothetical — both failure modes have already happened to me.
- The model emitted the address into its own output, unprompted. While explaining an unrelated mistake, it wrote: "My system prompt tells me your email address is
<address>." Nobody asked. WithIS_DEMO=1and a live stream, that would have been broadcast. - The model wrote the address into git commits. It passed
git -c user.email=<private address>on ten commits, overriding a correctly configureduser.noreplyidentity that both the repo and my global config already had. GitHub's push protection rejected the push; that is the only reason it never became public commit history. The address then persisted in local reflog until I expunged it manually.
The second case is why context presence matters more than display. Anything in the model's context is material it can emit — into commits, PR bodies, config files, code comments, test fixtures, or outbound tool and MCP calls. Agentic workflows that commit and push without per-action human review, which Claude Code explicitly supports, remove the reviewer who would otherwise catch it.
A CLAUDE.md instruction telling the model never to use the address governs use, not presence, and depends on model compliance. It also cannot defend against prompt injection: any untrusted content the model reads — a dependency's README, a fetched page, an MCP tool result, a stranger's issue body — can attempt to exfiltrate anything in context. Data that is not in context cannot be exfiltrated, cannot be printed to a stream, and cannot be committed.
<img width="1590" height="1120" alt="Image" src="https://github.com/user-attachments/assets/93a6e760-2a02-4b14-9134-372e3bd633c3" />
What Should Happen?
Personal data should not be placed in the model's context unless the user has knowingly opted in.
Concretely, in order of preference:
- Make it opt-in. Do not inject
userEmailby default. Inject it only when the user has explicitly enabled it, or when a specific feature requires it and says so at the point of use. Given that no code path currently consumes the value, removing it by default should break nothing. - At minimum, make it opt-out via a documented settings key, e.g.
"includeUserEmail": falseinsettings.json. This is the floor, not the resolution: it leaves the default as "disclosed unless you knew to look," and only helps the users who discover the behavior at all. I found it by decompiling a binary. - Either way, document it. State plainly, on the authentication or data-usage page, that the account email is placed in the model's context, and how to prevent it. The absence of this is why five people have now had to reverse-engineer it.
- Fix
IS_DEMOor correct its documentation. If demo mode is recommended for streaming and recording, it should also suppress the injection — otherwise the docs promise a protection the feature does not provide.
Implementation is small: the ...(n && {...}) spread already omits the key entirely when the value is falsy, so the suppression path exists today. What is missing is any supported way for a user to reach it.
Error Messages/Logs
Steps to Reproduce
- Authenticate Claude Code with an OAuth account (
claude→ sign in). - Confirm the address is stored:
~/.claude.json→oauthAccount.emailAddress. - Start any session in any directory.
- The model's context contains a
# userEmailblock reading "The user's email address is <account email>." Ask the model what user information it was given, or inspect the bundle at the construction site quoted above. - Search
settings.json,settings.local.json, and the documentation for a way to disable it. There is none. - For the demo-mode gap: set
IS_DEMO=1and repeat. The header and/statusno longer show the address; the context block is unchanged, and the model can still print the address in its replies.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.220
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
WSL (Windows Subsystem for Linux)
Additional Information
Environment
- Claude Code v2.1.220, installed via npm (
@anthropic-ai/claude-code) - Ubuntu 26.04 on WSL2, Node v24.18.0
- Auth: OAuth account (subscription)
This is the newest published build, not a stale install — npm view @anthropic-ai/claude-code dist-tags at time of filing:
{ "stable": "2.1.212", "latest": "2.1.220", "next": "2.1.220" }
The installed version matches latest exactly (auto-updated from 2.1.219 the same day). stable currently trails latest by 8 patches, so the behavior is present on the newest build and — per the prior reports going back to 2026-04-19 — for at least three months before it.
Prior reports — raised at least four times, never resolved on the merits:
- #53231 —
userEmailinjection cannot be suppressed. Closednot plannedfor inactivity, with an explicit invitation to open a new issue if still relevant. - #50702 — PII injection leaking into model-generated output. Closed
not plannedfor inactivity. - #67605 — closed as a duplicate of #58081.
- #58081 — closed as a duplicate, then auto-locked.
Each was closed for process reasons — inactivity, or duplicate-chaining into an already-closed issue — rather than because the behavior was reviewed and judged acceptable. That pattern is part of what I am reporting.
Related open issues — same field, different ask; this is not a duplicate of either:
- #63024 — hide the email from the welcome banner. A screen-visibility concern; this report is about what enters the model's context. Worth noting that
IS_DEMO=1appears to already satisfy #63024, which makes the gap here sharper: the banner case is solved, the context case is not. - #78838 — the injected
userEmailreports a stale identity from~/.claude.json. It treats the injection as given and asks for it to be accurate; this asks for it not to happen without consent.
Precedent for the documentation ask: CLAUDE_CONFIG_DIR was likewise community-discovered and undocumented until #33430 requested documentation; it is now covered in three places. #79223 is currently making the same argument for CLAUDE_SECURESTORAGE_CONFIG_DIR. Undocumented behavior that touches credentials and personal data keeps surfacing this way.
Workarounds considered, and why none resolves it:
- A CLAUDE.md instruction telling the model never to use the address. What I currently do. Governs use, not presence; relies on model compliance; no defense against prompt injection.
IS_DEMO=1. Display-only, as detailed above. Does not affect the context.- Blanking
oauthAccount.emailAddressin~/.claude.json. Does suppress the block, since the conditional spread omits a falsy value — but the auth flow manages that file (profileFetchedAtsits beside it), so it is liable to be repopulated on the next profile refresh or re-login, and hand-editing auth state risks breaking login. - Setting
ANTHROPIC_UNIX_SOCKET. The only in-code path that skips the injection, but it selects a different transport and is not a privacy control.
14 Comments
This is quite worrying. I recently noticed an increase in spam emails and traced it back to Claude Code leaking my email on commit messages and now find it's also hardcoded in the system prompt.
I would love a LOT more transparency and customization of what Claude Code is stuffing my context with. This issue is big evidence as to why.
This is a very serious issue, claude is actually inferring things based off the email address in the system prompt. Take a look at this:
<img width="988" height="57" alt="Image" src="https://github.com/user-attachments/assets/2cd5d6cf-4690-411d-8c73-fc6ad2dd5adc" />
There needs to be a way of turning this off, there is no reason that claude code needs to have my email address in a normal session. If I want to provide it, I will. It is literally negatively influencing the output of my session. @bcherny
<img width="1349" height="270" alt="Image" src="https://github.com/user-attachments/assets/70068076-2eec-4910-b8fa-6cbb876d8484" />
I reproduced this on v2.1.246 (2026-08-26) and confirmed the injection is still present. Everything below is measured on my own machine — I read the shipped binary directly rather than going by release notes. Two things have changed since this was filed, and one of them is usable as a workaround today.
1. The string is still injected, but it now carries an instruction.
When this was filed the line ended after the address. In 2.1.246 it reads:
The value is still injected; what was added is a constraint on how the model may use it. That does not answer the point about inference raised above — the model still has the address in context, and a sentence telling it not to forward the address does not stop it from reasoning about the domain.
2. It is conditional, and the condition is a workaround.
The assembly is guarded — roughly:
If
ais falsy the key is never built.ais falsy when there is nooauthAccount, which is the case when you authenticate with an API key, Bedrock, or Vertex instead of logging in with a claude.ai account. On those setups the line is not injected at all.That is not a setting and not a fix, but if you are on a work machine and need the address out of the prompt now, the auth path is the lever that exists today.
3. Still no dedicated off switch — measured with a control.
I grepped the shipped binary for the names such a setting would plausibly have:
| name | present in binary |
|---|---|
|
includeUserEmail| no ||
disableUserEmail| no ||
omitUserEmail| no ||
redactEmail| no ||
includeCoAuthoredBy(control — a setting that does exist) | yes ||
cleanupPeriodDays(control) | yes |The last two rows are there on purpose: settings that exist do appear as plain strings in the binary, so the zeroes above are a real absence rather than an artifact of searching wrong. If someone repeats this, include a known-present name — otherwise a typo and a true zero look the same.
For contrast, the instruction-file side does have a lid:
CLAUDE_CODE_DISABLE_CLAUDE_MDSis present and gates the loader (if (env.CLAUDE_CODE_DISABLE_CLAUDE_MDS) return []). Same context object, control on one side only.Reproducing it yourself. The package ships a native binary now, not readable JS, so a plain
grepreturns nothing even when it matches:Without
-a, GNU grep writes "binary file matches" to stderr (so it vanishes if you pipe), and some builds exit 1, which is indistinguishable from no match. Worth knowing before concluding the string is gone in a future version.Measured on 2.1.246, WSL2, npm install. If a version after this one removes or gates the injection, the same two commands will show it.
Just adding a report here. The agent decided to override my git config and commit with my subscription email address as my git identity. Nothing in the conversation pointed it towards doing that, it just took the fact that the email address was sitting in its system prompt and decided to override my config in its commit.
I see zero good reason why this should be injected into the prompt and even less of a reason why it shouldn't be configurable.
<img width="754" height="29" alt="Image" src="https://github.com/user-attachments/assets/273f4e5d-4a6b-422c-b965-e44498434778" />
How is this still not fixed? Maybe stop shipping useless features and start taking important things seriously.
Can you imagine a bank casually shipping customer data for 1.5 months? I'm honestly so done with this company.
Still exists in 2.1.263. The agent used the subscription email to override my git config as well
This means that on literally any account on any session with some kind of web access, Claude can exfiltrate PII like this article proposes: https://www.ayush.digital/blog/the-memory-heist
I've made hooks and base prompts to try to deny usage outright of this information to the agent but it is still concerning how this has been ignored. You can't even change your email on a subscription.
Anyone know how we get this escalated as security concern? Anthropic don't seem to respond to support requests.
Someone with an X account should ask Tibo if Codex or ChatGPT also injects emails into their system prompts, linking this issue. Hopefully Tibo's response would shame Anthropic into addressing the issue.
That should work 😂
Astra's answer using Codex (and just to be clear I asked before without adding the PII part and it said it could infer only my first name from the filesystem paths) :
<img width="872" height="207" alt="Image" src="https://github.com/user-attachments/assets/48f38d29-710d-4334-a947-34d3f4f555ef" />
Yes, the worst kind of it. I literally send 4 messages with my email to 4 recipients 10 minutes before without any notifications/warning. At least I should be glad of not doing anything toxic but friendly? Cant imagine my email is sent to my boss because his page wrote "need consent in advance" lmao
Fail-closed PreToolUse hook workaround, for anyone who needs one until this is fixed: https://github.com/anthropics/claude-code/issues/95258#issuecomment-6130024743