Skip to content

Teardown targets tmux windows by name; consider using the stable window id #212

Description

@mwotton

What happens today

Window teardown resolves its target from the window name
(src/multiplexer/types.rs:8-12):

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct WindowTarget {
    pub full_name: String,
    pub parent_session: Option<String>,
}

which becomes a name-based tmux target
(src/multiplexer/tmux.rs:98-103, 397-405):

fn window_target_arg(target: &WindowTarget) -> String {
    match target.parent_session() {
        Some(session) => format!("{}:={}", session, target.full_name),
        None => format!("={}", target.full_name),
    }
}

fn kill_window_target(&self, target: &WindowTarget) -> Result<()> {
    let target_arg = Self::window_target_arg(target);
    self.tmux_cmd(&["kill-window", "-t", &target_arg])
}

Callers: src/command/close.rs:109 and :115, src/workflow/cleanup.rs:491, :604, :790.

Credit where due: the = prefix forces tmux exact-name matching, so this is deliberately not a
fuzzy-prefix hazard, and the obvious "killed a similarly-named window" failure is already prevented.
The residual issue is narrower.

The residual problem

A window name is mutable after creation. It can change via tmux's automatic-rename, via a user
rename-window, or via an external terminal manager or session tool that relabels windows. When that
happens:

  • teardown fails to find the window, leaving an orphaned window behind; or
  • if the original name has since been reused by a different window, teardown targets the wrong one.

Meanwhile a stable identifier is already available and already parsed. LIVE_PANE_FORMAT
(tmux.rs:26) requests
#{window_id}, and parse_live_pane_line stores it
(tmux.rs:51-54):

window_id: parts
    .get(8)
    .map(|value| value.to_string())
    .filter(|value| !value.is_empty()),

tmux window ids (@N) are stable for the window's lifetime and are never reused within a server.
AgentState likewise keys on pane_key.pane_id (src/state/types.rs:76), which is similarly stable —
so the state store is already identity-keyed while teardown is name-keyed.

Why it matters for an automated consumer

An orchestrator that creates and destroys worktrees continuously accumulates orphaned windows whenever
teardown misses, and each orphan is a resource leak that eventually needs a separate sweeper. Teardown
is also destructive, so "resolved the wrong target" has an asymmetric cost — it's the one operation
where you'd most want identity rather than a label.

Proposed shape

Carry the stable id alongside the name and prefer it when present:

pub struct WindowTarget {
    pub full_name: String,
    pub parent_session: Option<String>,
    pub window_id: Option<String>,   // tmux "@N"
}

window_target_arg uses window_id when it's Some, and falls back to today's ={name} otherwise.
That keeps every existing call site working, needs no state migration (the id can be resolved at
teardown time from the same get_all_live_pane_info data already being fetched), and is a genuinely
small diff.

If you'd rather not widen WindowTarget, an alternative is to resolve name -> window_id once at the
start of teardown and use the id from then on, which would at least make the operation atomic with
respect to a concurrent rename.

Willing to send a PR

Yes — this one is small and self-contained, and we're happy to write it if you think it's worth having.
Equally happy to be told the exact-match behaviour is sufficient in practice and drop it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions