Summary
#310 fixed the window-level project tag on psmux. The session-level one looks to have the
identical bleed, through the identical mechanism, and nothing in #310/#315 touches it.
runs.prunable_sessions decides which bmad-loop-<run-id> agent sessions a prune may kill by
comparing each session's PROJECT_OPTION against runs.project_tag(project). The values come
from BaseTmuxBackend.session_options (adapters/tmux_base.py:163-181):
["list-sessions", "-F", f"#{{session_name}}\t#{{{option}}}"]
That is the same #{@name} format expansion #310 identifies as the window-side bug — it reads
app.user_options, of which psmux has exactly one map per server (types.rs:676;
format.rs:905-906). If a list-sessions row's #{@bmad_project} is evaluated by whichever
server answers the call rather than by the session that owns the row, every row carries one
value and the agent-session prune cannot discriminate projects — the session-level analogue of
the ctl-window bug #310 describes.
PsmuxMultiplexer overrides neither session_options nor set_session_option, so both ride
the base path today. The write side is also ungated: generic.py:433 writes
project_tag(project) — an absolute path — straight through set_session_option, with none of
the _transportable() refusal #315 added for the window channel, so an apostrophe- or
UNC-and-spaced project path can be stored corrupted and then never compare equal.
Blast radius
runs.prunable_sessions is what bmad-loop cleanup / the TUI's prune act on. A mis-scoped read
here is a kill decision on another project's live-or-dead agent session, which is strictly
worse than the ctl-window case: agent sessions host the coding-CLI processes.
Verify first — this is a hypothesis, not a confirmed defect
Three things to establish on a Windows host with psmux 3.3.7 before writing any code:
- Does psmux's
list-sessions enumerate sessions across all servers, or only the one it
routes to? (bmad-loop's list_sessions() assumes all — if psmux only answers for one, that
is a separate and larger problem for prunable_sessions.)
- If it does aggregate: is each row's
#{@bmad_project} resolved against that row's owning
server, or against the answering server's single map? Probe: tag two sessions with different
values, then psmux list-sessions -F '#{session_name}=[#{@bmad_project}]'.
- Does a session-scoped
set-option -t <session> @k <value> corrupt the same value shapes
_transportable() refuses? (Almost certainly yes — same CLI→server control line.)
If (2) bleeds, the fix has a ready shape: a session_options override in psmux_backend.py
that reads per session rather than through one format string, plus routing the write through
the same transport gate. Both would sit behind the backend seam, tmux untouched — the same
pattern #315 established.
Refs: #310, #315, #291, #254
Summary
#310 fixed the window-level project tag on psmux. The session-level one looks to have the
identical bleed, through the identical mechanism, and nothing in #310/#315 touches it.
runs.prunable_sessionsdecides whichbmad-loop-<run-id>agent sessions a prune may kill bycomparing each session's
PROJECT_OPTIONagainstruns.project_tag(project). The values comefrom
BaseTmuxBackend.session_options(adapters/tmux_base.py:163-181):That is the same
#{@name}format expansion #310 identifies as the window-side bug — it readsapp.user_options, of which psmux has exactly one map per server (types.rs:676;format.rs:905-906). If alist-sessionsrow's#{@bmad_project}is evaluated by whicheverserver answers the call rather than by the session that owns the row, every row carries one
value and the agent-session prune cannot discriminate projects — the session-level analogue of
the ctl-window bug #310 describes.
PsmuxMultiplexeroverrides neithersession_optionsnorset_session_option, so both ridethe base path today. The write side is also ungated:
generic.py:433writesproject_tag(project)— an absolute path — straight throughset_session_option, with none ofthe
_transportable()refusal #315 added for the window channel, so an apostrophe- orUNC-and-spaced project path can be stored corrupted and then never compare equal.
Blast radius
runs.prunable_sessionsis whatbmad-loop cleanup/ the TUI's prune act on. A mis-scoped readhere is a kill decision on another project's live-or-dead agent session, which is strictly
worse than the ctl-window case: agent sessions host the coding-CLI processes.
Verify first — this is a hypothesis, not a confirmed defect
Three things to establish on a Windows host with psmux 3.3.7 before writing any code:
list-sessionsenumerate sessions across all servers, or only the one itroutes to? (bmad-loop's
list_sessions()assumes all — if psmux only answers for one, thatis a separate and larger problem for
prunable_sessions.)#{@bmad_project}resolved against that row's owningserver, or against the answering server's single map? Probe: tag two sessions with different
values, then
psmux list-sessions -F '#{session_name}=[#{@bmad_project}]'.set-option -t <session> @k <value>corrupt the same value shapes_transportable()refuses? (Almost certainly yes — same CLI→server control line.)If (2) bleeds, the fix has a ready shape: a
session_optionsoverride inpsmux_backend.pythat reads per session rather than through one format string, plus routing the write through
the same transport gate. Both would sit behind the backend seam, tmux untouched — the same
pattern #315 established.
Refs: #310, #315, #291, #254