Compact fork-resume silently retires the session id — live children and external tooling keep the dead ancestor id, no signal announces the new one

Status Open
Reported on v2.1.216
Maintainer reply None cached
Activity 0 comments · opened Jul 21, 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?

Auto-compact (daemon path) relaunches the conversation as a new session id via fork-resume, but nothing running from before the compact ever learns this:

  • Pre-compact-spawned long-lived children (MCP servers, background processes) keep CLAUDE_CODE_SESSION_ID=<old> forever.
  • The interactive terminal client process keeps the old id in its env, so external tooling that resolves "which session is in this window" via env/proc inspection binds the dead ancestor.
  • The statusline inside the same terminal shows the new id — the UI and the env contradict each other.
  • The fork's SessionStart hook fires inside the daemon bg-pty-host, which has no terminal env ($KITTY_WINDOW_ID, $WINDOWID), so hooks that record terminal context at session start silently record nothing for the post-compact session.
  • Both .jsonl files continue to exist; the ancestor looks like a live-but-stalled session to any transcript-watching tool.

Impact: session-id-keyed tooling (statusline scripts, window/session binders, telemetry) mislabels the window with the pre-compact session and treats the ancestor as live indefinitely. MCP servers spawned pre-compact report against a dead session id.

The docs still describe /compact as replacing history within a session (https://code.claude.com/docs/en/sessions), and PreCompact semantics imply in-place; the fork-resume relaunch is undocumented.

What Should Happen?

Any of these would fix the class:

  1. Document the contract: compact may mint a new session id via fork-resume; <old>.jsonl becomes an ancestor.
  2. Emit a positive signal on fork-resume — fire SessionEnd (or a SessionForked event carrying old_session_idnew_session_id) for the ancestor, and/or a SessionStart with source: "compact" distinct from "fork". (An existing constituency for lifecycle signals: #47023.)
  3. Refresh CLAUDE_CODE_SESSION_ID where feasible (the client process), and document that pre-fork children hold a stale value.

Error Messages/Logs

# Observed process tree (v2.1.216, Linux) after an auto-compact:
  claude --resume "<session name>"                      # terminal client, env: CLAUDE_CODE_SESSION_ID=<OLD> claude daemon run --origin transient --spawned-by …
  claude bg-pty-host --bg-pty-host /tmp/cc-daemon-<uid>/<hash>/pty/<NEW8>.sock …  -- ~/.local/share/claude/versions/2.1.216  --session-id <NEW> --fork-session        --resume ~/.claude/projects/<slug>/<OLD>.jsonl --reply-on-resume …

  # Live env divergence at the same moment:
  #   fresh Bash-tool subshell:            CLAUDE_CODE_SESSION_ID=<NEW>   (correct)
  #   pre-compact MCP server processes:    CLAUDE_CODE_SESSION_ID=<OLD>   (stale, permanent)
  #   terminal client process environ:     CLAUDE_CODE_SESSION_ID=<OLD>   (stale)
  #   statusline JSON session_id:          <NEW>

Steps to Reproduce

  1. Start an interactive session; note $CLAUDE_CODE_SESSION_ID from a Bash tool call. Any MCP stdio server counts as a long-lived pre-compact child automatically.
  2. Drive the session to auto-compact (or trigger the daemon compact path).
  3. Compare: statusline session id (new) vs `tr '0' '

' < /proc/<client-pid>/environ | grep CLAUDE_CODE_SESSION_ID` (old) vs the MCP server's env (old).

  1. ps aux | grep fork-session: the conversation now runs as --session-id <NEW> --fork-session --resume <OLD>.jsonl — and no hook/event announced the transition.

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

~2.1.138 ?

Claude Code Version

2.1.216

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

Searched before filing — this exact defect is unreported. Closest:

  • #54508 (closed not_planned by the inactivity bot, never acknowledged): "session ID not preserved" across /compact, seen via the TUI task panel emptying — the same root cause, different symptom. This report is the second independent sighting, with the mechanism identified (--fork-session daemon relaunch) and /proc evidence.
  • #68534 (open): stale inherited CLAUDE_CODE_SESSION_ID at launch → silent transcript loss. Same silent-staleness class, different trigger. A commenter there independently observed the same detection signal: env id ≠ hook-payload session_id.
  • #77306 (open): --fork-session / /branch forfeit the prompt cache — corroborates the fork lifecycle minting new ids.
  • #59415 (closed): compact fork vs /resume picker; #78956 (open): SessionStart fork source undocumented.

View original on GitHub ↗