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.
What happens today
Window teardown resolves its target from the window name
(
src/multiplexer/types.rs:8-12):which becomes a name-based tmux target
(
src/multiplexer/tmux.rs:98-103,397-405):Callers:
src/command/close.rs:109and:115,src/workflow/cleanup.rs:491,:604,:790.Credit where due: the
=prefix forces tmux exact-name matching, so this is deliberately not afuzzy-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 userrename-window, or via an external terminal manager or session tool that relabels windows. When thathappens:
Meanwhile a stable identifier is already available and already parsed.
LIVE_PANE_FORMAT(
tmux.rs:26) requests#{window_id}, andparse_live_pane_linestores it(
tmux.rs:51-54):tmux window ids (
@N) are stable for the window's lifetime and are never reused within a server.AgentStatelikewise keys onpane_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:
window_target_arguseswindow_idwhen it'sSome, 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_infodata already being fetched), and is a genuinelysmall diff.
If you'd rather not widen
WindowTarget, an alternative is to resolve name ->window_idonce at thestart 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.