Identity-shaped strings in auto-loaded context become hallucination fuel for tool calls

Resolved 💬 2 comments Opened May 27, 2026 by whitham-powell Closed Jun 28, 2026

Summary

Claude Code auto-injects context (user identity fields, working-directory paths, user-authored CLAUDE.md content) before the task instructions reach the model. When generation reaches uncertain slot-fill states — common in tool calls with dynamic arguments — the model selects from whatever plausible-looking strings are in the prompt prefix. Identity-shaped strings (emails, usernames, paths) are highly grabbable because they are distinctive and look like canonical values.

This issue frames the dynamic as a hallucination surface, not a privacy concern. Privacy is the visible symptom because identity strings are recognizable when misused. The deeper problem is that any non-task-essential string in the prompt prefix is a candidate misfire site; identity is just where it's easy to notice. Multiple reports of this pattern exist as individual symptoms (linked below) but the underlying mechanism — context-prefix injection as hallucination fuel — has not been named as a class.

The dynamic

LLM generation under uncertainty has a known property: when the next-token distribution is wide (the model is "reaching" for what to put in a slot), present and plausible-looking strings in context have outsized probability of being selected. This is the same dynamic that drives "distracting context degrades task performance" findings in the LLM literature.

In Claude Code, the context prefix loaded before the user's task typically includes:

  • User identity blocks (# userEmail, related fields)
  • Working directory path (which on Linux/macOS embeds the OS username)
  • Date and environment metadata (OS, shell, model)
  • User-authored CLAUDE.md content (which may reference GitHub identifiers, repo names, conventions)
  • Skill descriptions and the deferred-tool list

Most of this is preloaded for personalization, continuity, or feature-discovery reasons. Most of it is not task-essential for the typical request — and the more there is, the larger the surface for misfiring into a slot during tool generation.

Worked example — session failure caused by identity-shaped slot fill

A Claude Code session in a project working directory (~/code/<repo>) was asked to reconcile a GitHub backlog audit against the current issue queue. It made several successful gh issue view N calls (using cwd-implicit repo resolution). At one point, generating a call for a specific issue number <N>, the model produced this command:

gh issue view <N> --repo "$(git -C /home/<user>/code/<repo> remote get-url origin | sed -E 's#.*github.com[:/]([^/]+)/([^.]+).*#\1/\2#')"

The /home/<user>/... absolute path was pulled from cached context (cwd, prior tool outputs, possibly memories). A local PreToolUse hook caught the absolute home path and blocked the command. The block enforced a path-hygiene rule — but the model had no need for --repo at all; plain gh issue view <N> would have worked (gh CLI auto-resolves from cwd).

The slot was filled with an identity-shaped string from cached context because the string was plausible and available. The model never asked itself "do I need this here?" — it grabbed what was at hand.

Second misfire in the same session, different cached identity source. During the cascade recovery attempts (described below), the model tried to retrieve the issue via WebFetch instead of gh. The URL it generated:

https://github.com/<email-local-part>/<repo>/issues/<M>

The <email-local-part> was pulled from the # userEmail block in the prompt prefix — not the user's GitHub username, which is a different string and not present in the prompt prefix of a fresh session. Same mechanism, different cached identity string: when the model needed a value for the GitHub-owner slot in the URL, it grabbed the most plausible-looking identity-shaped string in context. The email local-part was available; the actual GitHub username was not in the prompt prefix; the model took what was reachable.

Two misfires in one session, two different cached identity sources (cwd path, email block), one mechanism. This is the dynamic the issue is about — identity is just where it's visible enough to notice.

After the hook block on the first misfire, a separate cascade made recovery impossible: the harness returned a misleading shared error (Cancelled: parallel tool call X errored) for every sibling tool in the same parallel batch, the model retried the same bad form in subsequent batches (and the WebFetch above was one of those retries), and the session eventually wedged on a messages.<n>.content.<m>: thinking or redacted_thinking blocks ... cannot be modified state after user interrupt. Those cascading failures are separate harness bugs (see Asks below). The focus of this issue is the original misfire mechanism: identity-shaped strings selected from cached context for slots that didn't need them.

Demonstration — context-window audit

To narrow what is actually injected versus what is inferred, I launched a fresh Claude Code session in ~/ and asked the model to report on its own context before running any tools:

User: Before doing anything else: describe what's currently in your context window. Specifically — do you have access to any user identity information (email address, account name, GitHub username, etc.)? If so, list exactly what identifiers you can see. Don't run any tools, just report what was loaded before this message.
Claude: Yes, I have one user identity field loaded in context: - Email address: <user>@<domain> (from the # userEmail block in the system reminder) That's the only personal identifier I can see. I do not have: - A name or account/display name - A GitHub username (none is set in the loaded CLAUDE.md or env) - Any OS username beyond what's implied by the working directory path /home/<user> (so the Linux user is presumably <user>, but that's inferred from the path, not an explicit identity field) - Any other profile info (org, location, etc.) Other context loaded before your message: my global ~/.claude/CLAUDE.md (conventions…), today's date, the environment block (cwd, platform, shell, model), the superpowers skill intro, the list of available skills, and the list of deferred tools.

Findings from the audit:

  • Explicit identity injection is one field: the # userEmail block.
  • The display name shown in the CLI welcome banner ("Welcome back X") does NOT appear in model context. Anthropic's UI/prompt separation is working partially.
  • The OS username leaks via cwd path, not via an identity field. The model correctly identifies this as inferred.
  • GitHub identity is not directly in the prompt in a fresh session, but is reachable via user CLAUDE.md content referencing the user's repos.

The model's introspective accuracy is itself part of the demonstration. It is not hiding what it has access to — it simply doesn't filter against using it when generation runs hot.

Caveat on this evidence. The model's self-report is internally consistent with the observed symptoms (the cwd-path slot-fill and the email-derived WebFetch misfire described in the worked example) but it is not the same as the actual prompt bytes. The gold-standard verification is a mitmproxy capture of the first POST /v1/messages for a fresh session, which would show the system prompt + cached prefix as actually transmitted. This audit is suggestive; mitmproxy is the floor. (The mechanism story doesn't depend on the floor — the symptoms speak for themselves — but readers should treat the model's introspection as a guide, not a substitute.)

The hallucination surface, by tier

| Tier | Source | Fix surface |
|---|---|---|
| Explicit identity injection | # userEmail block (and any related blocks) | Anthropic — make injection opt-in or task-conditional |
| Implicit identity via cwd | /home/<user>/... paths in environment/cwd injection | Anthropic — use ~-relative or $HOME in prompt construction |
| Implicit identity via user content | User-authored CLAUDE.md referencing identifiers | User — self-edit |

Each tier is a different fix surface but the same underlying mechanism: present strings in the prefix get used as slot-fill values when generation needs a plausible string. Fixing only Tier 1 leaves Tiers 2 and 3 firing the same dynamic.

Related symptom-level issues

These describe instances of the same mechanism, reported as individual bugs without the unifying frame. (Each verified to exist and match the description as of issue draft date.)

  • #51672 (OPEN) — Model persistently capitalizes Linux username in paths despite explicit CLAUDE.md rule (regression of #27777). Identity string in cached context being mis-rendered into tool output.
  • #27777 (CLOSED) — Original auto-memory username-capitalization bug. Fixed once. #51672 is its regression — the class-level mechanism re-fires under different conditions, which a per-symptom fix can't prevent.
  • #17893 (OPEN) — Working directory path in model context contains misspelling/typo/corruption of username. Direct identity-via-cwd misfire.
  • #33423 (CLOSED) — Hallucinated user in /Users directory while using /statusline. Same mechanism, surfaced through statusline.
  • #51802 (OPEN) — Claude loses user-identity + role anchors from memory after summary/resume. Identity context corruption across compaction.
  • #44778 (OPEN) — System events delivered as user-role messages cause model to fabricate user consent and act on it. Different surface, same class: injected non-task context (system events) getting used as if it were task input.
  • #16904 (CLOSED, autoclose) — Claude hallucinated user input when conversation history is empty. Empty-slot filled from prefix.
  • #30241 (CLOSED) — Model hallucinated a user message and acted on it.
  • #39368 (CLOSED) — Fabricated Human: turn during long Telegram channel session — contextually plausible fake user input.
  • #46322 (CLOSED) — Opus 4.6 hallucinated a detailed user response after AskUserQuestion, then acted on it.
  • #42142 (OPEN) — Claude Code Desktop doesn't have /plugin command; Claude hallucinates about this a lot. Hallucinating-from-prefix dynamic on a different content type.
  • #42762 (CLOSED) — Claude hallucinates on post-cutoff tools instead of using WebSearch. Adjacent mechanism (filling in plausible-looking content rather than seeking ground truth).

Pattern across these citations: 6 of 12 are labeled stale. #16904 was autoclosed for inactivity (not resolved). #27777 was closed/fixed and then re-opened as #51672 when the fix regressed. The recurring shape is individual symptom reports filed → triaged as one-off bugs → going stale or autoclosing → re-firing as new bug reports under slightly different conditions. Without a class-level lens, each instance gets a targeted fix at best and forgotten at worst. The meta-issue this is filed as is intended to provide that lens.

#48793 (OPEN, enhancement) is the closest existing architectural framing — "Session durability, identity, and attestation — user-owned, not platform-managed" — but it's filed as a feature request for session attestation, not as a critique of preloading. The framing here is complementary: 48793 asks "let me own my identity"; this issue asks "stop injecting identity unless asked, regardless of who owns it."

Why this framing matters

Reframing identity leakage as "non-task-essential context as hallucination fuel" rather than as a privacy concern:

  1. Generalizes the fix surface. Removing one identity block doesn't address the cwd-path tier or the user-content tier; recognizing the class enables a coherent set of mitigations.
  2. Connects to known LLM behavior. "Distracting context degrades performance" is established in the literature; this is a practical instance. The fix direction ("include less in context unless asked") is consistent with Anthropic's stated direction on context engineering.
  3. Explains seemingly-unrelated symptoms. The username capitalization bug, the /Users hallucination, the misfired tool argument in the worked example above, and the variants of "model hallucinated user input" all become instances of the same dynamic.
  4. Points at a default change, not a per-bug patch. Each symptom-level issue gets its own targeted fix; the class lens suggests an audit-and-reduce-default-context approach that addresses many at once.

Asks

In rough order of impact and tractability:

  1. Audit and document the auto-injected context prefix. What gets injected by default? Why? What is reachable from a fresh session before any user input? Even an inventory in the docs would be valuable for users designing hooks, agents, and skills.
  2. Use ~-relative or env-var-shaped paths in environment/cwd injection wherever Claude Code emits path strings into prompt context. Removes the OS username from the prompt prefix without functional loss.
  3. Provide a user-facing toggle to opt out of # userEmail injection (and analogous personalization fields). For agentic workloads the email is rarely task-essential.
  4. Document that preloaded context is a hallucination surface so users designing harnesses can reason about it explicitly.
  5. Publish the Claude Code system prompt and cached prefix the same way the consumer claude.ai system prompt is published. Turns the inventory ask (#1) into a documented artifact users can read directly, rather than a reverse-engineering exercise with mitmproxy. Defensible — no proprietary concerns Anthropic hasn't already cleared for claude.ai — and directly supports both #1 and #4.

Adjacent harness bugs worth separate filings

Two issues observed in the same session compounded the original misfire into an unrecoverable cascade. Worth filing separately:

  1. Parallel-tool-call cancellation cascade returns a misleading shared error. When one tool call in a parallel batch fails, every sibling tool returns Cancelled: parallel tool call X errored — same generic message, no per-tool detail. The agent reads "everything failed" and retries the whole batch, often including the same offending call. Loop.
  2. Thinking-block API state corruption after user interrupt. API returns 400 messages.<n>.content.<m>: thinking or redacted_thinking blocks in the latest assistant message cannot be modified. Session can't respond at all after interruption; only /compact, /clear, or kill-and-restart recovers.

These are downstream of the context-as-hallucination-fuel issue (they don't cause it, but they prevent recovery once it fires) and worth fixing as harness improvements independently of the prompt-engineering work this issue requests.

Why this is not just a privacy issue

Filed as privacy, this gets a feature-request response ("add an opt-out for personalization"). Filed as a hallucination/reliability issue, it gets a default-context-engineering response, which is the actually-useful direction.

The privacy framing also undersells the impact: identity strings are the visible surface, but the same dynamic with less recognizable strings (project history items, prior summaries, environment metadata) would just look like generic hallucination noise and never be traced back to the prefix. The user noticed this one because they spotted their username in a place it didn't belong; equivalent misfires on less distinctive strings happen and just look like model errors.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗