[BUG] shutdown_approved pane-close handler is non-deterministic — same setup produces orphan in some runs
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When a teammate emits shutdown_approved (carrying paneId + backendType: tmux), the harness's pane-close handler is non-deterministic. Two identical clean-shutdown sequences produced different outcomes on a single test session: one closed the pane cleanly within 2 seconds, the other left an orphan bash holding the pane open after the teammate's claude had already exited.
When the close fails, the bash wrapper that launched claude is left at an interactive prompt. SIGTERM to that bash is ignored — only SIGKILL closes the pane. The teammate's claude process exits in both cases (so the harness thinks the teardown succeeded), but the tmux pane survives in the failing case.
What Should Happen?
shutdown_approved should reliably trigger pane closure via the paneId + backendType: tmux payload it carries. Pane count should drop within a few seconds of the response, every time.
If the underlying tmux operation can race / fail intermittently, the harness should verify the close actually landed and retry / escalate to a kill if it didn't.
Error Messages/Logs
Two runs of the identical reproduction had different polled timelines.
**Run A (clean — pane closed):**
[t+0s] panes=2 probe_pid=ALIVE team_dir=present
[t+1s] panes=2 probe_pid=ALIVE team_dir=present
[t+2s] panes=1 probe_pid=GONE team_dir=absent ← shutdown_approved fired AND pane closed
**Run B (orphan — pane stayed open, same setup):**
[t+0s ] panes=2 probe_pid=ALIVE team_dir=present
[t+1s ] panes=2 probe_pid=ALIVE team_dir=present
[t+2s ] panes=2 probe_pid=ALIVE team_dir=present
[t+3s ] panes=2 probe_pid=ALIVE team_dir=present
[t+5s ] panes=2 probe_pid=GONE team_dir=absent ← claude exited, team_dir wiped, but pane still open
[t+10s] panes=2 bash_pid=ALIVE team_dir=absent ← bash stayed alive holding the pane
Pane state inspection on the orphan via `tmux list-panes -a -F '#{pane_pid} #{pane_current_command} #{pane_dead}'`:
8503 2.1.126 0 ← orchestrator's pane (alive, expected)
71988 bash 0 ← orphan: claude exited, bash dropped to interactive prompt
Note `pane_current_command` for the orphan is now `bash` (was previously the versioned claude binary path), confirming claude exited and bash inherited control.
The harness reports the shutdown handshake succeeded:
{"type": "shutdown_approved", "requestId": "shutdown-...", "from": "probe", "paneId": "%8", "backendType": "tmux"}
…so from the orchestrator's view nothing went wrong. The orphan is invisible without external inspection.
Steps to Reproduce
- Create a fresh team:
````
TeamCreate({ team_name: "close-test" })
- Spawn a teammate that idles immediately:
````
Agent({
team_name: "close-test",
name: "probe",
subagent_type: "general-purpose",
prompt: "FIRST ACTION: ToolSearch select:SendMessage. Then SendMessage to team-lead 'ack'. Then go idle. When you receive {type: shutdown_request}, reply with shutdown_response."
})
- Wait ~15 seconds for the teammate to boot and ack.
- Send a clean shutdown_request (proper-object form):
````
SendMessage({ to: "probe", message: { type: "shutdown_request" } })
- Poll tmux pane state every 1s for 10 seconds. Do NOT call TeamDelete. Watch:
tmux list-panes -a -F '#{pane_pid} #{pane_current_command}'ps -ax -o pid,etime,command | grep <teammate-claude-pid>ls ~/.claude/teams/close-test/(auto-cleans either way)
- Observe outcome. Run the entire sequence (steps 1–5) multiple times. Some runs will produce a clean close (pane count 2 → 1 by t+2s); other runs will produce the orphan (pane count stays at 2 even after claude exits).
- Cleanup of the orphan:
kill -9 <bash-pid>from the polling output. SIGTERM is ignored.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.126
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
This is one of three related bugs in the Agent Teams teardown path (filed separately per the preflight checklist):
- Related Bug 1:
TeamDeletereturns success on a team with a shutdown-pending teammate, orphaning a pane via a different teardown race. - This issue (Bug 2):
shutdown_approved's pane-close handler is non-deterministic. - Related Bug 3: Orchestrator lead context never auto-releases after teammate exits — sticky status bar + blocked TeamCreate.
Bug 2 + Bug 3 compound: even after a "clean" shutdown, the user often has both an orphan pane AND a sticky lead context displaying the dead teammate's name in the status bar.
I cannot 100% rule out a race between the harness's pane-close call and tmux's own pane-handling. If that's the underlying cause, the suggested fix (verify-then-escalate) still resolves the user-visible symptom.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗