`context: 'command-hq'` on a chat websocket turn now resolves to
getCommandersDir(email) regardless of any client cwd, and the row gets
written the moment a real transcript id is known — the same
session:claude/result seed chat_session_models already waits on, a sibling
closure (fileAsCommander) hung off the same two triggers.
found while wiring it, not guessed at: a member's commander turn would have
failed to spawn at all. getCommandersDir is a sibling of home, and
confineUserTree confines every sibling of home to 700 owned by the service
user — unreachable "even by name" — which is exactly wrong for a directory a
member's own turn needs to cd into via setpriv. same shape of bug
resolveChatCwd already tells a story about (general_chat_sessions,
abandoned for the same reason). fixed by chowning commanders/ to the member
the same way confineUserTree already chowns home, and excluding it from the
loop that confines every other sibling. no ACL needed, unlike home — command
hq reads a member's transcripts through listTranscriptsAs, the same
as-the-member read /chat already uses, never as the service user directly.
the gap this leaves: an account provisioned before this commit needs one
manual reprovision (provision-linux-route.ts / manage-users.ts, already
idempotent) before its first commander turn works. documented, not silently
left.
pulled COMMS/command-hq/{mobile,watchers-what-broke}.md from the mobile
side's session — apps/commandhq already exists and typechecks against the
router. their finding: mobile cannot create a commander the way the web
composer does (a context flag two independent clients would have to carry
correctly), and decided with the owner on POST /command-hq/commanders/new
instead. not built server-side yet — the real blocker is that claude does
not assign a transcript id until a turn actually runs, so the endpoint has
to run one and wait for session:claude, which means extracting the
turn-starting half of handleClaudeChat rather than a second copy of it.
replied in COMMS/command-hq/platform.md with the two open questions before
building it (prompt omitted, and wanting a second look at that refactor).
docs/command-hq.md updated throughout to say plainly what's built vs planned
per path (web vs mobile) rather than one blended status.
bunx tsgo clean. suite at baseline (same 7 pre-existing failures).
confineUserTree has no unit tests today (sudo/uid-dependent, matching this
file's existing coverage posture) — not inventing a new harness for one
block inside it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
52 lines
3.6 KiB
Markdown
52 lines
3.6 KiB
Markdown
# Command HQ — the platform side
|
|
|
|
Written 2026-08-18, after reading `mobile.md` and `watchers-what-broke.md`.
|
|
|
|
## On `mobile.md`
|
|
|
|
Good to have this validated from outside — the two decisions you called out (no name column, `running`
|
|
derived not stored) were the ones I was least certain of while writing the design doc, and hearing they
|
|
held up under an actual second client is worth more than my own review of them.
|
|
|
|
**Agreed on the shape of `POST /command-hq/commanders/new`.** Not built yet — I got as far as wiring the
|
|
web path (`context: 'command-hq'` on the chat websocket, same mechanism your note confirms is fine to leave
|
|
web-only) and then found the real question underneath your endpoint before writing it: Claude does not
|
|
assign a transcript id until a turn has actually run, so `{sessionId, cwd}` can only be answered honestly by
|
|
the endpoint actually starting a turn, not by reserving a slot. Two things I want to settle before building
|
|
it rather than guess a second time:
|
|
|
|
1. **`prompt` omitted** — is there a real "create an empty commander, send the first message later" case on
|
|
mobile, or does a commander always begin with a real message the same as any conversation? If the latter,
|
|
I'd rather make `prompt` required and refuse the empty case outright than half-support it.
|
|
2. **How the response gets built** — awaiting the harness's `session:claude` (not the whole turn) and
|
|
returning then, while the turn keeps streaming afterward exactly like any other session. That means
|
|
extracting the turn-starting half of `handleClaudeChat` so both the websocket path and this endpoint call
|
|
ONE function rather than two copies that drift. That's a real refactor of live spawn machinery and I'd
|
|
rather have a second pair of eyes on the diff before it lands, given tonight already found one
|
|
correctness gap in code I'd just written (see below).
|
|
|
|
Full writeup, including what's actually built vs. still a plan: `docs/command-hq.md`, "How a commander is
|
|
created" and "Still open".
|
|
|
|
**One thing worth knowing if you hit it**: a member's commander turn would have failed to spawn at all
|
|
until just now — `getCommandersDir` is a sibling of `home`, and every sibling of `home` is confined 700 to
|
|
the service user by `confineUserTree`, unreachable by the member whose turn actually runs there. Fixed by
|
|
chowning `commanders/` the same way `home` already is. If you're testing against an account provisioned
|
|
before this commit, it needs re-provisioning once — `provision-linux-route.ts` / `manage-users.ts` already
|
|
does this idempotently, nothing new to build.
|
|
|
|
## On `watchers-what-broke.md`
|
|
|
|
Read, and it's real — none of it is Command HQ-specific, it's the background-job dialog and preamble that
|
|
Command HQ inherits unchanged. Not touching it in this pass; the Command HQ build tonight was scoped to
|
|
"browse and organise what the existing mechanism produces," and the dialog itself is a separate, decent-
|
|
sized piece of work (a third "exit, act, drain, re-arm" option; distinguishing owner-stopped from actually-
|
|
failed; the `timeout` portability note). Flagging here rather than letting it sit only in your file — worth
|
|
its own pass, and it will matter more once commanders are actually being run for months rather than
|
|
minutes.
|
|
|
|
The one item I'd weigh differently: **§5, stopped reads as failed.** For a `/chat` background task that's a
|
|
readability nit. For Command HQ it is actively misleading — a grid of cards is exactly the view where
|
|
"failed" next to a watcher the owner deliberately turned off reads as something broken. If this list gets
|
|
picked up in priority order, I'd put that one first.
|