Hmbown/CodeWhale · error
tmux session remains active after kill-session ( )
Error message
tmux session {session} remains active after kill-session ({status}) What it means
After `stop_tmux_session` sends `tmux kill-session`, it re-checks the session state via `tmux_session_state`. If the session is still Present, the kill did not take effect and the runtime refuses to claim the lane stopped — a Running record would otherwise lie about a live process. The accompanying comment notes a nonzero kill exit can be benign when process and session exit simultaneously; the explicit absence check, not the kill exit code, is the source of truth.
Solutions
- Check tmux server options for `remain-on-exit` or respawn hooks: `tmux -S <socket> show-options -g remain-on-exit` and disable them.
- Verify no supervisor inside the session (e.g. a wrapper script loop) restarts the pane command; kill the underlying processes instead.
- Confirm the lane record's pinned tmux_socket matches the socket actually used to kill the session.
- As a last resort, kill the whole tmux server for that socket (`tmux -S <socket> kill-server`) and then reconcile the lane.
Example fix
// before (session persists due to remain-on-exit) $ tmux -S /tmp/lane.sock set -g remain-on-exit on // after $ tmux -S /tmp/lane.sock set -g remain-on-exit off
Defensive patterns
Strategy: retry
Validate before calling
// after kill-session, verify absence before proceeding
let state = tmux_session_state(socket, session)?;
if matches!(state, TmuxSessionState::Present) {
eprintln!("session still present; check remain-on-exit and respawn hooks");
} Prevention
- Keep `remain-on-exit off` on servers hosting lane sessions.
- Avoid supervisor loops inside lane session commands.
- Ensure the record's pinned socket matches the socket used at start.
When it happens
Trigger: Calling `start` or `stop` on a tmux lane when `kill-session` reports success (or is tolerated as benign-failure) but the follow-up `has-session` still finds the session — e.g. tmux respawned the session via `remain-on-exit`, another client recreated it, or the kill targeted a different socket than the one queried.
Common situations: A tmux hook or `remain-on-exit on` in the server config keeps the session object alive; a supervisor script inside the session restarts it; concurrent tooling re-attaches and recreates the session between kill and check; a pinned tmux_socket mismatch in the lane record makes kill and check hit different servers.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- lane ` ` was stopped before tmux dry-run start completed
- lane ` ` was stopped before tmux launch
- tmux has-session for
- tmux new-session failed with
- Agent not found
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/49d12c7fbb8a1249.
Report an issue: GitHub.
Appendix: source
Thrown at crates/lane/src/runtime.rs:610
)
}
fn stop_tmux_session(socket: &Path, session: &str) -> Result<()> {
let status = tmux_command(socket)
.args(["kill-session", "-t", session])
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.with_context(|| format!("kill tmux session {session}"))?;
match tmux_session_state(socket, session).with_context(|| {
format!(
"confirm tmux session {session} on {} stopped after kill-session ({status})",
socket.display()
)
})? {
TmuxSessionState::Absent => {}
TmuxSessionState::Present => {
bail!("tmux session {session} remains active after kill-session ({status})")
}
}
// A nonzero kill can be benign when the process and session exited in the
// same instant. The explicit absence check above is the source of truth.
Ok(())
}
fn tmux_log_proxy_command(
proxy: &Path,
command: &[String],
log_path: &Path,
receipt_path: &Path,
receipt_tmp_path: &Path,
environment_path: Option<&Path>,
lane_id: &str,
) -> String {
let mut argv = vec![
proxy.display().to_string(),View on GitHub (pinned to 73e0f67d83)