[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

Status Fixed / completed
Reported on v2.1.162
Maintainer reply None cached
Activity 3 comments · opened Jul 19, 2026 · closed Jul 30, 2026

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:

  1. transcript file written on /exit (89 KB);
  2. parsed — all three turns present, including the final one, plus the /exit record;
  3. 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 inherited CLAUDE_CODE_* variables and write fine. A nested claude -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 SessionEnd hooks completing with status 0, followed by ~/.claude.json being written atomically three times. It is a fully normal shutdown; the transcript write simply never happens.
  • The multiplexer deleting filesstrings on the herdr binary shows no code path touching .claude/projects or .jsonl; the upstream maintainer has confirmed the integration only writes a hook script and reports session_id over a socket. Independently: a Codex CLI session running in an adjacent pane of the same multiplexer writes its rollout .jsonl incrementally and survives intact, so the pane environment is perfectly capable of hosting a durable agent session.
  • Retention cleanupcleanupPeriodDays is 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:

  1. 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 --resume coming 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.
  2. 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.
  3. 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).

View original on GitHub ↗

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