resume existing Claude sessions with --resume, not --session-id
test / unit + widget + golden + a11y (push) Failing after 32s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 32s
test / unit + widget + golden + a11y (push) Failing after 32s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 32s
Confirmed root cause of the dead-pane bug: `claude --session-id <id>`
rejects an id that already exists ("Session ID … is already in use") and
exits. The primary pane uses a deterministic id to resume across restarts,
and /resume re-binds to an existing id — both relaunched with --session-id,
so whenever the tmux session wasn't already alive (clean boot, or after
/clear+/resume) Claude exited instantly and the pane had no live backend:
typed input vanished while the transcript still rendered. The pane now
launches an existing session (transcript on disk) with `--resume <id>` and
only a brand-new one with `--session-id <id>`. Fresh secondaries and /clear
(fresh ids) were always fine. Verified empirically against a live session.
T-161.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -207,21 +207,26 @@ class _ClaudePaneState extends State<ClaudePane> {
|
||||
final home = Platform.environment['HOME'] ?? '';
|
||||
final transcriptFile = '$home/.claude/projects/${repoRoot.replaceAll('/', '-')}/$_sessionId.jsonl';
|
||||
|
||||
// A transcript already on disk means this session existed before, so we
|
||||
// resume it; otherwise it's new. This drives both the self-heal kill and
|
||||
// the launch flag — `claude --session-id <id>` REFUSES an existing id
|
||||
// ("already in use"), so an existing session must launch with `--resume`
|
||||
// (T-161).
|
||||
final transcriptExists = await File(transcriptFile).exists();
|
||||
|
||||
// Self-heal (T-147): if no transcript is bound to our session id, any
|
||||
// clide tmux session of this name is stale (created before --session-id
|
||||
// clide tmux session of this name is stale (created before session-id
|
||||
// binding, or otherwise unconnectable) and `new-session -A` would
|
||||
// attach to it and leave the pane stuck waiting forever. Kill it so a
|
||||
// clean session is created with our --session-id.
|
||||
//
|
||||
// Safe by construction: this only ever kills clide's OWN session — by
|
||||
// its exact `clide-claude-<slug>` name, on the private `-L clide`
|
||||
// socket the user's terminal claude never runs on — and never deletes
|
||||
// any transcript file. A healthy session's transcript already exists,
|
||||
// so re-attach (D-41 continuity) is preserved.
|
||||
if (!await File(transcriptFile).exists()) {
|
||||
// clean session is created. Safe by construction: only ever kills clide's
|
||||
// OWN `clide-claude-<slug>` session on the private `-L clide` socket, and
|
||||
// never deletes any transcript. A healthy session's transcript exists, so
|
||||
// re-attach (D-41 continuity) is preserved.
|
||||
if (!transcriptExists) {
|
||||
await tmux.killSession(_sessionName!);
|
||||
}
|
||||
|
||||
final launch = claudeLaunchArgs(_sessionId!, resume: transcriptExists);
|
||||
final tmuxConf = await _ensureTmuxConf();
|
||||
const cols = _cols;
|
||||
const rows = _rows;
|
||||
@@ -239,9 +244,7 @@ class _ClaudePaneState extends State<ClaudePane> {
|
||||
'$cols',
|
||||
'-y',
|
||||
'$rows',
|
||||
'claude',
|
||||
'--session-id',
|
||||
_sessionId!,
|
||||
...launch,
|
||||
];
|
||||
|
||||
// CLAUDE_CODE_NO_FLICKER=1 enables claude's fullscreen TUI mode:
|
||||
@@ -260,7 +263,7 @@ class _ClaudePaneState extends State<ClaudePane> {
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
argv = ['claude', '--session-id', _sessionId!];
|
||||
argv = launch;
|
||||
resp = await ipc.request('pane.spawn', args: {
|
||||
'argv': argv,
|
||||
'kind': PaneKind.claude.wire,
|
||||
|
||||
@@ -68,6 +68,12 @@ String _hash(String s) {
|
||||
/// workspace re-binds the same `<uuid>.jsonl` across restarts (resume).
|
||||
String primarySessionId(String repoRoot) => _deterministicUuid(primarySessionName(repoRoot));
|
||||
|
||||
/// The `claude` argv to launch [sessionId]: `--resume` an existing session,
|
||||
/// or `--session-id` to create a new one. `--session-id` REFUSES an id that
|
||||
/// already exists ("Session ID … is already in use") — so resuming a pane
|
||||
/// whose transcript already exists must use `--resume` (T-161).
|
||||
List<String> claudeLaunchArgs(String sessionId, {required bool resume}) => resume ? ['claude', '--resume', sessionId] : ['claude', '--session-id', sessionId];
|
||||
|
||||
/// A fresh random session id for a secondary pane — secondaries are
|
||||
/// always clean sessions, never resumed.
|
||||
String freshSessionId() {
|
||||
|
||||
Reference in New Issue
Block a user