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
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:
@@ -2193,3 +2193,4 @@ Acceptance: a secondary tab shows its OWN fresh conversation, independent of the
|
||||
', NULL, '2026-05-23 06:19:51', '2026-05-23 06:19:51', '2026-05-23 06:19:51', NULL, '9e46b8eec7cc5e42132249a81d4925d1', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-146', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 06:19:51', '2026-05-23 06:19:51', '2026-05-23 06:19:51', NULL, 'dd4ec6cfbbc3118cd4cf02604b91108b', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-146', 'status', 'in_progress', 'done', NULL, '2026-05-23 06:30:47', '2026-05-23 06:30:47', '2026-05-23 06:30:47', NULL, '93f9c6814b9e82e5b60ac0e57fff5afe', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-147', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 07:03:18', '2026-05-23 07:03:18', '2026-05-23 07:03:18', NULL, 'b0bc1949f98ef05a05717cef80ba194f', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
|
||||
@@ -2550,3 +2550,5 @@ Open question to validate live: whether `claude --session-id <existing-uuid>` re
|
||||
|
||||
Acceptance: a secondary tab shows its OWN fresh conversation, independent of the primary; the primary keeps its conversation; both update live. Regression of T-137; relates to D-41.
|
||||
', 'done', 'high', NULL, NULL, NULL, '2026-05-23 06:19:51', '2026-05-23 06:30:47', NULL, '2e1843c9b540a3d7ced03052d9efcbc4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-147', 'bug', 'T-132', 'Claude pane gets stuck waiting on a stale/legacy session', 'When the bound transcript (<munged-cwd>/<session-id>.jsonl) never appears — e.g. attaching via new-session -A to a session created before --session-id binding (T-146), or any unconnectable session — the pane shows ''Waiting for Claude…'' forever. Fix: before spawning, if the bound transcript for our deterministic session id does not exist, kill the clide tmux session of that exact name so new-session creates a clean session with our --session-id. SAFETY (hard requirement): only ever kill clide-initiated sessions — by exact clide-claude-<slug> name, on the private -L clide socket (a user''s terminal claude is never on that socket); never delete any transcript file. Also makes clean-install/first-run robust. Live-verify the stuck primary self-heals on relaunch and a secondary still gets a fresh independent session.', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-23 07:03:18', '2026-05-23 07:03:18', NULL, '21f7ebec0ddcab48a3ea7e8be0c0e979', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-148', 'task', 'T-132', 'Claude transcript disk-usage indicator + cleanup button', 'Surface transcript/session disk usage in the Claude panel with a cleanup affordance so the user can pick and choose what to remove. Per-session sizes (the <munged-cwd>/*.jsonl files + subagents/), a total, and a button to delete selected sessions'' transcripts. User-driven only — clide never auto-deletes transcript content. Future/''at some point'' per user. Relates to T-146 (session ids) and D-75.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-23 07:03:18', '2026-05-23 07:03:18', NULL, '7bbe7f4baf80027516cc46cff93a8dfe', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
|
||||
@@ -120,6 +120,11 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Claude pane no longer gets stuck on "Waiting for Claude…" when its
|
||||
session can't be bound (T-147) — e.g. a session left over from before
|
||||
session-id binding, or a fresh machine. It now retires that stale
|
||||
clide session and starts a clean one. Only clide's own `-L clide`
|
||||
sessions are touched (never a terminal Claude, never transcript files).
|
||||
- Secondary Claude tabs showed the primary's conversation instead of
|
||||
their own (T-146). Each pane now binds to its own session via
|
||||
`claude --session-id`, so concurrent sessions in one workspace stop
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user