[Bug] Inherited stale CLAUDE_CODE_SESSION_ID causes silent transcript loss without warning

Status Fixed / completed
Reported on v2.1.177
Maintainer reply ✓ Yes — bcherny
Activity 6 comments · opened Jun 15, 2026 · closed Aug 17, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

Bug Description
Title: Inherited stale/cross-project CLAUDE_CODE_SESSION_ID causes silent transcript loss (no --resume entry, no warning)

What happens:
If a long-lived process is ever started from a shell inside a Claude Code session, it permanently inherits CLAUDE_CODE_SESSION_ID, CLAUDE_CODE_CHILD_SESSION=1, CLAUDE_CODE_EXECPATH, etc. Every claude later launched as a descendant of that process
treats itself as a child of the original session — which by then is dead and belongs to a different project — and writes no transcript to ~/.claude/projects/.... The session runs and responds normally; it just silently never persists and never
appears in claude --resume. No warning is shown, so it's indistinguishable from data loss.

In my case the long-lived process was the Plank dock (started once from inside a Claude session during a reboot recovery). It then poisoned every app launched from the dock — including the IDE → its integrated terminal → claude. All terminal
sessions in that IDE stopped persisting, while plain-terminal sessions were unaffected.

Environment:

  • Claude Code 2.1.177, native install, claude doctor clean
  • Linux (Ubuntu Budgie), JetBrains/PyCharm integrated terminal
  • Inherited vars pointed at a dead session from a different project, created days earlier on v2.1.174

Repro:

  1. In a Claude session, note $CLAUDE_CODE_SESSION_ID.
  2. From that shell, launch a long-lived process (e.g. restart your dock/panel/IDE), then exit Claude.
  3. Launch claude as a descendant of that process (e.g. a terminal it spawns). It inherits the old CLAUDE_CODE_SESSION_ID and CLAUDE_CODE_CHILD_SESSION=1.
  4. Hold a conversation → no .jsonl appears in the project dir, and the session is absent from claude --resume.
  5. unset CLAUDE_CODE_SESSION_ID CLAUDE_CODE_CHILD_SESSION CLAUDE_CODE_EXECPATH CLAUDE_CODE_SSE_PORT before claude → persistence returns.

Expected:
When claude starts with an inherited CLAUDE_CODE_SESSION_ID that is stale (its session is gone) or belongs to a different project/cwd, it should start a fresh persisted session — or at minimum print a visible warning that the conversation will not
be saved. Today it silently produces no transcript and no --resume entry, which looks identical to data loss.

Workaround (environment-side, for reference):
The stale vars lived in a long-running desktop process (the Plank dock) that had been started from inside a Claude session, so it poisoned every app it launched. Cleared without logging out by relaunching the dock from the systemd user manager's
clean environment:

```
pkill -x plank; sleep 1
systemd-run --user --unit=plank-dock1 --collect plank
systemd-run --user --unit=plank-dock2 --collect plank -n dock2

  
then fully quit and relaunch the IDE from the now-clean dock

After this, the IDE terminal showed only a fresh CLAUDE_CODE_SSE_PORT (integration still active) and claude persisted normally. This is a user-side mitigation; it does not address the silent-failure behavior in the CLI.

**Environment Info**
- Platform: linux
- Terminal: gnome-terminal
- Version: 2.1.177
- Feedback ID: 32085ba1-6bd1-4518-8378-3d8772b2c12f

**Errors**
```json
[]

View original on GitHub ↗

5 Comments

yurukusa · 4 months ago

This is a sharp diagnosis, and I can confirm the poisoning vector from my own machine. In a shell running inside Claude Code, exactly these are exported:

CLAUDE_CODE_SESSION_ID=<uuid>
CLAUDE_CODE_CHILD_SESSION=1
CLAUDE_CODE_ENTRYPOINT=cli
CLAUDE_CODE_EXECPATH=<path>

Any process spawned from that shell inherits all of them by ordinary POSIX inheritance — so your chain (Claude session → dock → IDE → integrated terminal → claude) carrying a dead, cross-project CLAUDE_CODE_SESSION_ID is fully consistent with this. The descendant treats itself as a child of a session that no longer exists, and silently writes no transcript.

User-side detection (works today, before the upstream fix): in any terminal where persistence matters, check whether it's poisoned before trusting it —

env | grep CLAUDE_CODE_

If you see CLAUDE_CODE_SESSION_ID / CLAUDE_CODE_CHILD_SESSION in a terminal you did not just launch from inside claude, that terminal's claude sessions won't persist.

User-side fix / workaround:

unset CLAUDE_CODE_SESSION_ID CLAUDE_CODE_CHILD_SESSION CLAUDE_CODE_EXECPATH CLAUDE_CODE_ENTRYPOINT
# then launch claude in that clean shell

The durable fix is to start long-lived processes (dock/panel/IDE) from a shell that was not spawned inside a Claude session, so they never capture these vars in the first place.

Confirm it actually persisted (the same silent-non-persistence failure mode as #68435): after a session, check that a real transcript is growing, not a stub —

find ~/.claude/projects -name '*.jsonl' -newermt '-10 minutes' \
  -exec sh -c 'echo "$(wc -c <"$1") bytes  $1"' _ {} \;

A healthy session's .jsonl is KB–MB. If nothing new appears (or it's frozen at ~110 bytes), the session never persisted — which is the data loss, made visible.

What's verified vs. upstream: the env inheritance and the unset workaround I confirmed directly. What only Anthropic can fix is the silent part — a descendant inheriting a stale/cross-project CLAUDE_CODE_SESSION_ID should detect that the parent session is dead (or belongs to another project) and either start a fresh persisted session or warn, instead of running without ever writing a transcript. Until then, the env | grep check is the cheapest guard against mistaking this for permanent data loss.

dorrington · 4 months ago
This is a sharp diagnosis, and I can confirm the poisoning vector from my own machine.

Thank you, but I can not take the credit. Claude did most of the heavy lifting here. I just asked (hopefully) insightful questions and added context and guidance.

User-side fix / workaround: `` unset CLAUDE_CODE_SESSION_ID CLAUDE_CODE_CHILD_SESSION CLAUDE_CODE_EXECPATH CLAUDE_CODE_ENTRYPOINT # then launch claude in that clean shell ``

Indeed, yes, but seems to have the side effect of breaking the IDE integration, if, for example, running inside a PyCharm terminal.

Necmttn · 3 months ago

Startup should validate inherited Claude session env before suppressing transcript creation. If CLAUDE_CODE_CHILD_SESSION or CLAUDE_CODE_SESSION_ID is set, check that the parent session exists, is active or recently active, and matches cwd/project lineage.

On mismatch, warn and start a new top-level session while recording stale_env_session_id. That preserves child-session behavior for real descendants but prevents a dock/IDE process from turning future sessions into silent non-persisted children.

---

_Generated with ax._

lokkaflokka · 3 months ago

We hit what looks like the same root cause with a different symptom: two concurrently live sessions sharing one CLAUDE_CODE_SESSION_ID.

Setup: several parallel claude sessions in separate terminal tabs (macOS/iTerm, native install; some tabs launched via caffeinate claude). One tab was spawned from a non-clean environment (a shell descended from another live session, or a restored pane), so its environment already carried a live sibling session's CLAUDE_CODE_SESSION_ID. The fresh claude (a brand-new conversation, no --resume and no --session-id) adopted the inherited id instead of minting a new one.

What we observed:

  • Anything keying on the env var sees two distinct live sessions as the same one. Our external session-tracking scripts mis-bound the new session onto the sibling's identity in real time.
  • Hook payloads (.session_id in PreToolUse stdin JSON) carried the correct, distinct id for each session — only the env var was wrong. That divergence (env id ≠ hook-visible id) turned out to be a useful detection signal, and might be one for you too.
  • A plain fresh terminal tab tested clean (CLAUDE_CODE_SESSION_ID unset), consistent with your finding that the leak rides long-lived intermediary processes.

+1 to the expected behavior as stated, with one addition: on startup with an inherited CLAUDE_CODE_SESSION_ID that is (a) stale, (b) from a different project/cwd, or (c) currently in use by another live session, claude should mint a fresh id absent an explicit --resume/--session-id — or at minimum print a visible warning. Case (c) is the one that bit us: the inherited id wasn't stale, it was live, so the two sessions collided while both were running.

bcherny collaborator · 1 month ago

Thanks for the detailed report and repro — I retested this on the released 2.1.233 on Linux.

What I found:

  • CLAUDE_CODE_SESSION_ID=<stale> CLAUDE_CODE_CHILD_SESSION=1 claude -p "say hi" now generates a fresh session id and does write a transcript under ~/.claude/projects/….
  • An interactive session with the same inherited env still skips transcript saving (that part is intentional, so sessions Claude itself spawns don't fill your --resume list) — but it's no longer silent: the footer shows "Transcript saving is off — inherited CLAUDE_CODE_CHILD_SESSION marker · restart with CLAUDE_CODE_FORCE_SESSION_PERSISTENCE=1 to keep future transcripts".
  • Setting only CLAUDE_CODE_SESSION_ID (no child marker) has no effect — a fresh id is used and the transcript is saved.

The silent-loss behavior you hit was fixed in 2.1.217 ("Added warnings when … session saving is off due to an inherited environment variable, instead of losing transcripts silently" — changelog). For a top-level session that inherited the marker from a long-lived launcher/dock, set CLAUDE_CODE_FORCE_SESSION_PERSISTENCE=1, or remove CLAUDE_CODE_CHILD_SESSION from that process's environment.

Closing as fixed — reply here if you still see silent transcript loss on 2.1.217 or later and we'll reopen.

🤖 Generated with Claude Code

Showing cached comments. Read the full discussion on GitHub ↗