[BUG] Transcript persistence is silently skipped depending on the hosting terminal — controlled A/B (multiplexer pane vs tmux) isolates the terminal as the only variable
What happened
When Claude Code runs inside a pane of the herdr terminal multiplexer, the session transcript .jsonl is never written — not during the session, and not on a clean /exit. The session is silently and permanently unrecoverable: --resume cannot find it, and no error or warning is shown at any point.
The same Claude Code binary, same version, same user, same machine, same environment variables and same cwd persists transcripts normally when launched from tmux instead.
Failure signature (constant across every failing run)
~/.claude/session-env/<sid>/ ✅ created
~/.claude/projects/<slug>/<sid>/tool-results/ ✅ created and populated
~/.claude/projects/<slug>/<sid>.jsonl ❌ never created
Everything written eagerly during the session survives; only the transcript — the part that appears to be buffered and flushed later — is missing. ~/.claude/history.jsonl also does not receive the session's prompts.
Controlled experiment
Four runs, driven programmatically (herdr socket API / tmux send-keys) so the interaction is identical. Each: new session, 2–3 turns, clean /exit.
| # | Terminal | cwd | HERDR_* + CLAUDE_CODE_* env | transcript |
|---|---|---|---|---|
| A | herdr pane | ~ | present | ❌ none |
| B | herdr pane (--debug-file) | ~ | present | ❌ none |
| C | tmux, launched from inside a herdr pane | scratch dir | present (inherited) | ✅ 83 KB |
| D | tmux, launched from inside a herdr pane | ~ | present (inherited) | ✅ written |
C and D inherit the full herdr environment: HERDR_ENV, HERDR_PANE_ID, HERDR_SOCKET_PATH, plus an inherited CLAUDECODE=1 / CLAUDE_CODE_CHILD_SESSION=1 / CLAUDE_CODE_SESSION_ID=… set (the multiplexer's server process carries these, so every pane it spawns inherits them). They persist normally.
→ The only variable that changes the outcome is the terminal/PTY itself.
The passing control was verified end to end
To be sure "passes" means more than "a file exists", the tmux control was re-run with three individually identifiable turns:
- transcript file written on
/exit(89 KB); - parsed — all three turns present, including the final one, plus the
/exitrecord; claude --resume <id>re-launched and the full conversation rendered correctly.
The failing (multiplexer) runs produce no file at all, so there is nothing to resume.
Process and terminal state are effectively identical
Comparing the claude process in a multiplexer pane vs in tmux (ps -o pid,ppid,pgid,sess,tpgid,tty,stat, stty -a, ancestor chain): both have a controlling tty, both are in the foreground process group, neither is a session leader, ancestor chains are structurally identical (claude → shell → multiplexer server → launchd), and terminal line settings match. The only two differences are the pane subtree running at low priority (SN+ vs S+) and one input flag (-iutf8 vs iutf8) — neither can plausibly prevent a file write.
Corroborating this: during a failing run, ~/.claude.json is written atomically three times at shutdown. File I/O is fully functional at exactly the moment the transcript is not written.
Ruled out (each with a control, not by inference)
- Environment variables — runs C and D carry the identical
HERDR_*and inheritedCLAUDE_CODE_*variables and write fine. A nestedclaude -p …spawned from a Bash tool call inside a herdr pane (same env) also writes its transcript normally. - cwd / project slug — run D uses the same
~cwd as run A, opposite result. - Multiplexer auto-detection (cf. #70219) — the debug log explicitly shows no multiplexer detected:
DECSTBM: enabled (TMUX=unset ZELLIJ=unset TERM_PROGRAM=<redacted> TERM=xterm-256color)
and the process command line is a bare claude with no injected --teammate-mode.
- Unclean shutdown — the debug log shows all
SessionEndhooks completing with status 0, followed by~/.claude.jsonbeing written atomically three times. It is a fully normal shutdown; the transcript write simply never happens. - The multiplexer deleting files —
stringson the herdr binary shows no code path touching.claude/projectsor.jsonl; the upstream maintainer has confirmed the integration only writes a hook script and reportssession_idover a socket. Independently: a Codex CLI session running in an adjacent pane of the same multiplexer writes its rollout.jsonlincrementally and survives intact, so the pane environment is perfectly capable of hosting a durable agent session. - Retention cleanup —
cleanupPeriodDaysis set to 3650.
Debug log
--debug-file on a failing run produces zero transcript-related entries — no append, write, flush, open, or error concerning the session .jsonl. The persistence code path appears never to be entered. (Same observation as #70219.)
What I am asking for
I am not claiming to know where the defect lives — the trigger may well be something the multiplexer does to its PTY, and I have shared these findings with that project too. What I can show is that Claude Code's transcript persistence has an undocumented, invisible dependency on the hosting terminal, and that when the dependency is not met it fails silently and totally. Three asks, in priority order:
- Do not fail silently. Whatever the trigger, if a session will not be persisted, surface it — at startup, on exit, or both. Today the only feedback is
--resumecoming up empty, potentially hours later, with~/.claude/projects/<slug>/<sid>/sitting there full of sidecar files that make it look like the session was recorded. Even if the root cause turns out to be external, this part is fixable here and would have prevented the loss. - Document or remove the dependency. If persistence is legitimately conditional on some terminal property, that condition should be documented and ideally logged under
--debug-file(today the debug log contains zero transcript-related entries, so there is no way to diagnose this from the outside). If it is not intentional, it is a bug. - Consider writing incrementally rather than buffering until exit (see #70632), so that any abnormal death costs the last turn rather than the entire session.
Impact
Silent, total, unrecoverable loss of whole sessions. Nothing warns the user during the session, and ~/.claude/projects/<slug>/<sid>/ exists with sidecar files — which makes it look like the session was recorded.
This has been hit before and misdiagnosed: herdr #454 ("Claude sessions lost!", Fedora/kitty, CC 2.1.162) was closed on the correct observation that the multiplexer does not touch ~/.claude/projects — but the actual mechanism was never identified, so it remains unfixed.
Environment
- Claude Code: 2.1.215 (latest at time of filing; native install)
- OS: macOS
- Terminal: herdr 0.7.4,
TERM=xterm-256color - Control terminal: tmux (same machine, same shell, same env) — works
Related
- #70632 — transcript not written until session exit (deferred flush). Likely the same subsystem; this report is the case where the flush never happens at all.
- #70219 — multiplexer auto-detection disables persistence. Different trigger — that one shows an injected
--teammate-mode; here no multiplexer is detected and no such flag is injected. - #63545 — transcript not saved under tmux. Also different: plain tmux persists correctly on this setup (runs C and D above).
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗