fix Claude secondary pane copy + false "session exited"

Two things surfaced in the secondary pane: the tab said "session 1"
while the banner said "secondary 1" — now both say "session N". And the
banner showed "session exited" right after starting, even though Claude
was alive: a transient tmux client process can exit during spawn while
the session itself is fine. pane.exit now verifies via `tmux has-session`
and only reports exited when the session is actually gone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-23 09:48:26 +02:00
co-authored by Claude Opus 4.7
parent af483c2c34
commit f65ef50379
6 changed files with 34 additions and 2 deletions
+10 -2
View File
@@ -275,10 +275,18 @@ class _ClaudePaneState extends State<ClaudePane> {
final kernel = _kernel();
if (kernel == null) return;
// Lifecycle only — content comes from the transcript, not pane.output.
_eventSub = kernel.events.on<DaemonEvent>().listen((e) {
_eventSub = kernel.events.on<DaemonEvent>().listen((e) async {
if (e.subsystem != 'pane' || e.data['id'] != _paneId) return;
switch (e.kind) {
case 'pane.exit':
// A transient tmux client can exit (e.g. during spawn/respawn)
// while the session — and Claude — stay alive. Don't report
// "exited" then; only when the tmux session is actually gone.
// (The no-tmux fallback has no session, so the exit is real.)
if (_usingTmux && _sessionName != null && await tmux.hasSession(_sessionName!)) {
return;
}
if (!mounted) return;
setState(() => _statusLine = widget.isPrimary ? 'session exited — restart clide to retry' : 'session exited');
case 'pane.closed':
_paneId = null;
@@ -317,7 +325,7 @@ class _ClaudePaneState extends State<ClaudePane> {
child: ConversationView(
controller: _conversation!,
emptyState: ClaudeBanner(
role: widget.isPrimary ? 'primary' : 'secondary ${widget.secondaryIndex}',
role: widget.isPrimary ? 'primary' : 'session ${widget.secondaryIndex}',
workspace: _repoRoot,
statusLine: _statusLine,
),
+8
View File
@@ -24,6 +24,14 @@ Future<void> killSession(String name) async {
await tmuxRunner([..._socket, 'kill-session', '-t', name]);
}
/// Whether [name] is a live session on the clide socket. Used to suppress
/// a spurious "session exited" when a transient tmux client process exits
/// but the session itself is fine (T-149 follow-up).
Future<bool> hasSession(String name) async {
final r = await tmuxRunner([..._socket, 'has-session', '-t', name]);
return r.exitCode == 0;
}
/// Return the names of all sessions currently alive on the clide
/// socket. Empty list if the server is not running.
Future<List<String>> listClideSessions() async {