a dead watch wakes its commander instead of staying dead
the machine rebooted 2026-08-24 at 12:40 and took the deploy commander's loop with it. a release was tagged the next day and sat there. the only evidence was a word missing from a card. the obvious fix is for the platform to respawn the loop, and docs/command-hq.md settled on 2026-08-18 that it must not: the agent is only notified about a process it started and is tracking, so a platform-spawned watch would run correctly, fire, and reach nobody. that trades a visible failure for an invisible one, which is worse — not watching is at least legible. so the platform notices and hands the turn back. the agent re-arms itself, the way it already does after every fire, and the process it starts is tracked. the owner's two conditions hold by construction: the commander is still the only thing that starts a watch, and the indicator still reads the pid file. DETECTION is the pid file, not watcher_armed. that column is a claim only one path sets and most commanders never take it — both commanders here read false while one was demonstrably watching. a pid file that exists with nothing alive behind it means a watch that was running and is not. which made stop ambiguous, so stopCommanderProcesses now removes the file. without that, a deliberate stop looks exactly like a crash and the reviver would ask the commander to re-arm what the owner just switched off. the commander now keeps WATCHERS.md — what each watch is, the exact command, the interval, what it does when it fires — because when the wake arrives the transcript is long past the turn that armed it and the platform deliberately does not remember the command. a commander predating the file may rebuild from JOB.md, a script in its directory, or the turn that armed it; inventing one it cannot find is forbidden, since a wrong watch looks like a working one. the owner is notified separately from the commander being woken. they are different audiences and only one of them can act when the wake does not work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -9,6 +9,7 @@ export {
|
||||
listCommandHqSessions,
|
||||
listCommandHqSessionIds,
|
||||
listCommandHqOwnedSessionIds,
|
||||
listAllCommanders,
|
||||
getCommandHqSession,
|
||||
setCommandHqSessionCategory,
|
||||
deleteCommandHqSession,
|
||||
|
||||
@@ -213,6 +213,28 @@ export async function listCommandHqOwnedSessionIds(userId: number): Promise<Set<
|
||||
// drizzle's `eq` cannot express `IS NOT NULL` either.
|
||||
const isNotNullSessionId = () => sql`${commandHqSessions.sessionId} is not null`;
|
||||
|
||||
/**
|
||||
* Every commander on the machine, across every account — id, owner and directory only.
|
||||
*
|
||||
* The one query here that is not scoped to a user, and it exists for a caller that is not a request:
|
||||
* `servers/api/command-hq/watcher-revive.ts` ticks over all of them looking for a watch whose process
|
||||
* has gone. There is no `userId` to scope by at that point, and the reviver needs the owner's id in
|
||||
* order to resolve their email and their home — so it is returned rather than looked up per row.
|
||||
*
|
||||
* Narrow on purpose. A background sweep has no business reading names, categories or cost, and the next
|
||||
* caller that wants those should say so by widening deliberately.
|
||||
*/
|
||||
export async function listAllCommanders(): Promise<{ userId: number; sessionId: string; dirName: string }[]> {
|
||||
const rows = await db
|
||||
.select({
|
||||
userId: commandHqSessions.userId,
|
||||
sessionId: commandHqSessions.sessionId,
|
||||
dirName: commandHqSessions.dirName,
|
||||
})
|
||||
.from(commandHqSessions);
|
||||
return rows.filter((row): row is { userId: number; sessionId: string; dirName: string } => row.sessionId !== null);
|
||||
}
|
||||
|
||||
export async function getCommandHqSession(userId: number, sessionId: string): Promise<CommandHqSessionRow | null> {
|
||||
const [row] = await db
|
||||
.select(SESSION_ROW_COLUMNS)
|
||||
|
||||
Reference in New Issue
Block a user