self-heal Claude panes stuck on an unconnectable session (T-147)
test / unit + widget + golden + a11y (push) Failing after 27s
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 28s

After session-id binding (T-146), a pane that attached (via new-session
-A) to a session created before the change — which has no --session-id,
so its transcript is under a different id — would wait forever for a
transcript that never appears. Same on any unconnectable session.

Before spawning, if no transcript exists for our deterministic session
id, kill the clide tmux session of that exact name so new-session
creates a clean one with our --session-id. This self-heals the stuck
state on next launch and makes clean-install/first-run robust.

Safe by construction: only clide's own session is killed — by its exact
clide-claude-<slug> name on the private -L clide socket (a terminal
claude never runs there) — and no transcript file is ever deleted. A
healthy session's transcript already exists, so re-attach continuity
(D-41) is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-23 09:05:27 +02:00
co-authored by Claude Opus 4.7
parent b224502658
commit b52283b7c0
4 changed files with 26 additions and 2 deletions
+18 -2
View File
@@ -150,6 +150,24 @@ class _ClaudePaneState extends State<ClaudePane> {
// Secondary: fresh → always a clean session.
_sessionId ??= widget.isPrimary ? primarySessionId(repoRoot) : freshSessionId();
final home = Platform.environment['HOME'] ?? '';
final transcriptFile = '$home/.claude/projects/${repoRoot.replaceAll('/', '-')}/$_sessionId.jsonl';
// 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
// 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()) {
await tmux.killSession(_sessionName!);
}
final tmuxConf = await _ensureTmuxConf();
const cols = _cols;
const rows = _rows;
@@ -222,8 +240,6 @@ class _ClaudePaneState extends State<ClaudePane> {
// showing the primary's conversation (T-146). Each pane gets its own
// bus channel so their controllers don't cross-talk.
final messages = _kernel()!.messages;
final home = Platform.environment['HOME'] ?? '';
final transcriptFile = '$home/.claude/projects/${repoRoot.replaceAll('/', '-')}/$_sessionId.jsonl';
final channel = ClaudeConversation.sessionChannel(_sessionId!);
_feed = TranscriptPublisher(
messages: messages,