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

  1. Check tmux server options for `remain-on-exit` or respawn hooks: `tmux -S <socket> show-options -g remain-on-exit` and disable them.
  2. Verify no supervisor inside the session (e.g. a wrapper script loop) restarts the pane command; kill the underlying processes instead.
  3. Confirm the lane record's pinned tmux_socket matches the socket actually used to kill the session.
  4. 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

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


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)