[BUG] Account email (PII) is injected into the model's system prompt without consent, disclosure,

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 5 comments · opened Jul 25, 2026

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.jsonoauthAccount.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.

  1. 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. With IS_DEMO=1 and a live stream, that would have been broadcast.
  2. The model wrote the address into git commits. It passed git -c user.email=<private address> on ten commits, overriding a correctly configured user.noreply identity 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:

  1. Make it opt-in. Do not inject userEmail by 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.
  2. At minimum, make it opt-out via a documented settings key, e.g. "includeUserEmail": false in settings.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.
  3. 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.
  4. Fix IS_DEMO or 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

  1. Authenticate Claude Code with an OAuth account (claude → sign in).
  2. Confirm the address is stored: ~/.claude.jsonoauthAccount.emailAddress.
  3. Start any session in any directory.
  4. The model's context contains a # userEmail block 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.
  5. Search settings.json, settings.local.json, and the documentation for a way to disable it. There is none.
  6. For the demo-mode gap: set IS_DEMO=1 and repeat. The header and /status no 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 — userEmail injection cannot be suppressed. Closed not planned for inactivity, with an explicit invitation to open a new issue if still relevant.
  • #50702 — PII injection leaking into model-generated output. Closed not planned for 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=1 appears to already satisfy #63024, which makes the gap here sharper: the banner case is solved, the context case is not.
  • #78838 — the injected userEmail reports 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.emailAddress in ~/.claude.json. Does suppress the block, since the conditional spread omits a falsy value — but the auth flow manages that file (profileFetchedAt sits 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.

View original on GitHub ↗

4 Comments

sammcj · 1 month ago

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.

Reithan · 29 days ago

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.

Coice · 10 days ago

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

Coice · 10 days ago

<img width="1349" height="270" alt="Image" src="https://github.com/user-attachments/assets/70068076-2eec-4910-b8fa-6cbb876d8484" />

Showing cached comments. Read the full discussion on GitHub ↗