[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.
Showing cached comments. Read the full discussion on GitHub ↗
4 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" />