Recognise an empty tmux server's answer for a missing session
A running but empty server answers "no current target". hasSession only knew "can't find session", "no server running" and "no sessions", so it returned that as a real error. Kill then failed for a pane that was already gone, the quarantine never cleared, and the worker's only session slot stayed pinned. This is a side effect ofdea56e4. Before the runtime became its own unit the server exited with its last session and answered "no server running", which hasSession already handled, so this reply had never been produced. AgentStatus shares the same helper and now reports "exited" instead of erroring. Found live: the F28 disposable task's session survived its own block on93338b7, quarantined and unreapable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011xsXyr5J1RACo71YeKG3Pu
This commit is contained in:
@@ -133,7 +133,15 @@ func (b *TmuxBackend) hasSession(ctx context.Context, session string) (bool, err
|
||||
return true, nil
|
||||
}
|
||||
message := strings.ToLower(string(out) + " " + err.Error())
|
||||
if strings.Contains(message, "can't find session") || strings.Contains(message, "no server running") || strings.Contains(message, "no sessions") || (strings.Contains(message, "error connecting to") && strings.Contains(message, "no such file")) {
|
||||
// "no current target" is what a *running but empty* server answers: the
|
||||
// target does not resolve and there is no current session to fall back to.
|
||||
// It only started appearing once the runtime became its own unit running
|
||||
// `tmux -D`, which keeps the server alive past its last pane (F32). Before
|
||||
// that the server exited with its last session and answered "no server
|
||||
// running", so this branch was never reached. Without it Kill returns an
|
||||
// error for a pane that is already gone, quarantine never clears, and the
|
||||
// worker's only session slot stays pinned.
|
||||
if strings.Contains(message, "can't find session") || strings.Contains(message, "no server running") || strings.Contains(message, "no sessions") || strings.Contains(message, "no current target") || (strings.Contains(message, "error connecting to") && strings.Contains(message, "no such file")) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
|
||||
@@ -421,3 +421,27 @@ func TestPaneProgressIgnoresInputLines(t *testing.T) {
|
||||
t.Fatal("real harness output left the progress digest unchanged")
|
||||
}
|
||||
}
|
||||
|
||||
// TestMissingSessionIsRecognisedOnAnEmptyRunningServer guards F32. A running
|
||||
// but empty tmux server answers "no current target", not "no server running".
|
||||
// The runtime only stays alive past its last pane since it became its own unit
|
||||
// running `tmux -D`, so this reply had never been seen before. Treating it as
|
||||
// a real error made Kill fail for a pane that was already gone, which left the
|
||||
// quarantine set and pinned the worker's only session slot.
|
||||
func TestMissingSessionIsRecognisedOnAnEmptyRunningServer(t *testing.T) {
|
||||
for _, message := range []string{"no current target", "can't find session: x", "no server running"} {
|
||||
bin := filepath.Join(t.TempDir(), "tmux")
|
||||
script := "#!/bin/sh\necho \"" + message + "\" >&2\nexit 1\n"
|
||||
if err := os.WriteFile(bin, []byte(script), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := &TmuxBackend{Binary: bin}
|
||||
if err := b.Kill(context.Background(), Session{PaneID: "s:1.0"}); err != nil {
|
||||
t.Fatalf("%q: killing an already-gone session failed: %v", message, err)
|
||||
}
|
||||
status, err := b.AgentStatus(context.Background(), Session{PaneID: "s:1.0"})
|
||||
if err != nil || status != "exited" {
|
||||
t.Fatalf("%q: status=%q err=%v, want exited", message, status, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user