Skip to content

fix: skip state apply when resolved pane has no window#7

Open
colinperel wants to merge 1 commit into
accessd:mainfrom
colinperel:fix-empty-window-id-guard
Open

fix: skip state apply when resolved pane has no window#7
colinperel wants to merge 1 commit into
accessd:mainfrom
colinperel:fix-empty-window-id-guard

Conversation

@colinperel

Copy link
Copy Markdown

Problem

scripts/agent-state.sh has a time-of-check/time-of-use race. After
resolve_target_pane() validates a pane exists, the very next line reads its
window:

pane_id=$(resolve_target_pane "$agent")
window_id=$(tmux display-message -p -t "$pane_id" '#{window_id}')

The pane can die in that gap — the common trigger is an agent's own pane
closing the instant its Stop/done hook fires (shell exits, pane
auto-killed). display-message -t <dead-pane> '#{window_id}' then returns
empty, so window_id="".

The apply paths below run tmux set-window-option -t "$window_id" …. With an
empty -t target, tmux falls back to the currently active window, so the
finished agent paints an unrelated window's window-status-style (e.g. stuck
red done). Worse, the bookkeeping (TMUX_AGENT_WINDOW_<id>_*, the _DONE
marker, saved original style) is keyed by window_id, so it lands under the
empty string. pane-focus-in.sh looks up the real window id, finds nothing,
and can never clear the style — the window stays tinted until reset_all.sh.

clear_window_title_style() already guards [ -z "$window_id" ] && return;
the apply path does not. So the plugin can set a style on an empty/active-window
target that its own clear path can never match.

Fix

Guard the empty window_id once at the source — a resolved pane with no live
window has no target to style, so exit cleanly. Covers the title, border, and
pane-style branches with a single check.

Testing

  • bash -n + shellcheck -s bash on the patched script: pass.
  • Manually reproduced the stuck-style state via the dead-pane race and confirmed
    the guard prevents the mis-keyed paint.

Related: #5 (sub-agent sessions flashing the main window state) is a different
cause but the same "wrong window gets painted" family.

resolve_target_pane() can return a pane that passes its existence check
but dies before window_id is read on the next line -- e.g. an agent's
pane closes at the moment its Stop hook fires `done`. window_id then
comes back empty.

The apply paths below issue `tmux set-window-option -t "$window_id"`.
With an empty target, tmux applies the option to the *currently active*
window, painting an unrelated window's status style. The bookkeeping
(TMUX_AGENT_WINDOW_<id>_* env, DONE markers) is keyed to the empty id,
so pane-focus-in.sh can never match and clear it -- the window stays
tinted (e.g. stuck red `done`) with no way to reset short of reset_all.

clear_window_title_style() already guards `[ -z "$window_id" ]`; the
apply path did not. Guard once at the source: an empty window_id means
the resolved pane has no live window, so there is no target to style.
Copilot AI review requested due to automatic review settings June 30, 2026 01:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a TOCTOU race in scripts/agent-state.sh where a target pane can die after being resolved, causing window_id to become empty and tmux window styling to accidentally apply to the currently active window.

Changes:

  • Adds an early guard to exit when window_id is empty after resolving the target pane/window.
  • Documents the underlying race condition and why an empty -t target is dangerous.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/agent-state.sh
@@ -369,6 +369,17 @@ esac

pane_id=$(resolve_target_pane "$agent")
window_id=$(tmux display-message -p -t "$pane_id" '#{window_id}')
Comment thread scripts/agent-state.sh
Comment on lines +373 to +381
# A resolved pane can die between resolve_target_pane's existence check and
# here (e.g. the agent's pane closes as its Stop hook fires `done`). That
# leaves window_id empty, and the apply paths below would fall through to
# `set-window-option -t ""`, which tmux applies to the *current* window --
# painting an unrelated window with bookkeeping keyed to the empty id, so
# focus-in can never clear it. No live target means nothing to do.
if [ -z "$window_id" ]; then
exit 0
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants