Fixing a regression I introduced last night. Commander rows in the Live list were changed to open in /chat rather than navigating to Command HQ — right, and it cost the composer its commander controls, because `isCommander` was computed from the ROUTE: `manager.context === 'command-hq'`. So the same commander, rendered on a different screen, silently lost "exit and hand back, then re-arm" — the only fire behaviour a watcher wants, and the one the dialog makes default inside a commander. GET /chat/sessions/:id now reports isCommander, derived from the session's own cwd by the same isCommandersDir already used by the sessions list and /chat/live. The answer travels with the conversation. The composer reads one value, manager.isCommander, true when EITHER source says so — and both are needed. The screen knows when Command HQ starts a commander, before a session or a transcript exists for any route to report on. The session knows once it does, wherever it is opened. Screen-only was this bug; session-only would have broken the brand-new composer, which is where the whole thing starts. Verified on a real commander rather than by reading: 172cdd8c resolves cwd=…/commanders and isCommander=true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2.4 KiB
id, title, theme, status, created, date_source, order
| id | title | theme | status | created | date_source | order |
|---|---|---|---|---|---|---|
| known-bugs/a-commander-opened-from-chat-loses-its-context | A commander opened from /chat loses its commander controls | known-bugs | done | 2026-08-21 | stated | 4 |
A commander opened from /chat reports the ordinary chat context, so the composer hides every control
that only exists for a commander. The visible symptom is the background-job dialog offering "Stop when
it fires" and "Keep going" but not "Exit and hand back to the agent, then re-arm" — the one a watcher
actually wants, and the dialog's own default inside a commander.
Introduced 2026-08-21 by a deliberate UX change: a commander in the Live list opens in /chat rather than
navigating away to Command HQ, because being thrown out of the page you are reading costs more than the
metadata you did not ask for. That change is right and should stay.
The defect is that isCommander is derived from the screen rather than from the session:
isCommander={manager.context === 'command-hq'}, and /chat passes no such context. A commander is a
commander wherever it is rendered.
The fix is to carry it on the session. ClaudeSessionSummary and /chat/live both already have
isCommander, derived server-side from the cwd; GET /chat/sessions/:id does not, and that is the one the
chat detail reads. Adding it there and letting the composer take it from the session rather than the route
closes this and makes the same thing work in any future surface that renders a chat.
Related: the same context name means two different things either side of EmbeddableChat — a useChat
option and a component prop, neither reading the other's — which is what hid the original bug twice.
Fixed 2026-08-22. GET /chat/sessions/:id now reports isCommander, derived from the session's own cwd
by the same isCommandersDir the list and /chat/live already used — so the answer travels with the
conversation instead of with the screen showing it.
The composer reads ONE value, manager.isCommander, true when either source says so. Both are needed and
neither is sufficient: the SCREEN knows when Command HQ starts a commander, before a session or a transcript
exists for any route to report on, and the SESSION knows once it does, wherever it is opened. Screen-only
was this bug; session-only would have lost the brand-new composer.
Verified on a real commander: 172cdd8c resolves cwd=…/commanders and isCommander=true.