From 211b32ef1a5e0a16d3a95dc4c68f17545cf8e3df Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Fri, 13 Nov 2026 20:02:05 -0600 Subject: [PATCH] fix(cli): f6c re-check, a failed released-marker write still removes the start lock; docs name the new warning and code (#1873) --- docs/TOOLS.md | 2 +- packages/cli/src/org-console.mjs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 4f1aef260..9ef3406de 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -348,7 +348,7 @@ The hire form and the autonomy panel from a terminal (E2, #1873, P1-3). `mosaic The bus and Console lifecycle verbs refuse inside an agent run (agent-guard, #1873): `mosaic bus start`, `bus stop`, `bus restart` and `bus recover`, and `mosaic console start`, `console stop` and `console restart`, and `mosaic org archive` and `org delete`, which stop the organization's bus, and `mosaic org restore`, which starts it (`org restore --no-bus` starts nothing and is not refused), and `mosaic org role-templates`, `hire`, `fire`, `change` and `autonomy` (E2). Each exits 3 with `refused: this runs only from a human shell, outside any agent run (found MARKER in the environment)` before it reads anything. It checks the environment it is given and its own process environment, so a sanitized environment does not hide a marker. A usage error (a missing or extra argument, `--pm` with `--no-launch`, or a bad `--dev-insecure-bind`/`--dev-allow-host`) still comes first (exit 4). `mosaic bus status` and `mosaic console status` stay open to agents. The Console's Restart bus and Restart Console controls keep working: the Console starts its bus host with its own environment, which carries no marker, and Restart Console relaunches the Console under its supervisor without going through the CLI. The markers are the ones `packages/cli/src/transport.mjs` lists. The check is cooperative: a process that drops the markers from its environment is not stopped by it. -Refusal lines (E2E6, #1873). A refusal that has a next step prints it on a second line, `mosaic: next: ...`, after the unchanged message, so callers that match the message keep working. `mosaic bus status`, `start`, `stop` and `restart` on a stale bus host lock exit 3 with `mosaic: stale bus host lock ...` and `mosaic: next: if the bus host died, clear it with mosaic bus recover ORG; recover refuses while the host still runs`. A human-transport verb whose bus is not running (no socket, or the connect refused before anything was sent) exits 3 with `bus-not-running: the bus for ORG is not running, so the request was not sent` and a next line naming `mosaic bus start ORG` and `mosaic bus recover ORG`; `outcome-unknown` (exit 1) stays for a request that left and got no answer. `mosaic org definitions` with an unknown organization, an unknown or malformed name, or a bad id prints one refusal line (exit 3; `unknown-organization` only when the organization file itself is missing) or, for a bad id, the usage (exit 4), with no stack trace and no host path. Pack, board and workflow refusals carry their next step as `template save` does. `mosaic console start` and `console stop` clear a `console/start.lock` whose named supervisor (pid and start time) is gone, once, with a `cleared a console start lock left by process PID, which is gone` warning; a lock whose holder still runs, or that names no holder, refuses and stays. A start or stop releasing its lock marks it released through its own descriptor and removes the path only while it is still that file, so a lock put back after its release is cleared the same way. An `autonomy-precondition` refusal from the bus carries the check that failed (owner notices off, no tested reverse, a resolver without the right, no `review.verdict` holder, no enforced budget, out of the loop only from near the loop, the trail review or near-loop decision missing); the human-transport child writes only the code, so a terminal `org autonomy set` shows the Console's message. `mosaic org template save` writes free text verbatim: it never rewrites prose, and the preview lists every field that mentions the organization id, a project id, an agent name or the owner's account, with the names it mentions, for the owner to edit before sharing. Saving under a name already saved for the account refuses `template-exists` (exit 2, nothing written) with a next line to save under another name or list the saved ones with `mosaic org template list ORG`. +Refusal lines (E2E6, #1873). A refusal that has a next step prints it on a second line, `mosaic: next: ...`, after the unchanged message, so callers that match the message keep working. `mosaic bus status`, `start`, `stop` and `restart` on a stale bus host lock exit 3 with `mosaic: stale bus host lock ...` and `mosaic: next: if the bus host died, clear it with mosaic bus recover ORG; recover refuses while the host still runs`. A human-transport verb whose bus is not running (no socket, or the connect refused before anything was sent) exits 3 with `bus-not-running: the bus for ORG is not running, so the request was not sent` and a next line naming `mosaic bus start ORG` and `mosaic bus recover ORG`; `outcome-unknown` (exit 1) stays for a request that left and got no answer. `mosaic org definitions` with an unknown organization, an unknown or malformed name, or a bad id prints one refusal line (exit 3; `unknown-organization` only when the organization file itself is missing, `definitions-unreadable` when `templates` cannot read the roles directory) or, for a bad id, the usage (exit 4), with no stack trace and no host path. Pack, board and workflow refusals carry their next step as `template save` does. `mosaic console start` and `console stop` clear a `console/start.lock` whose named supervisor (pid and start time) is gone, once, with a `cleared a console start lock left by process PID, which is gone` warning; a lock whose holder still runs, or that names no holder, refuses and stays. A start or stop releasing its lock marks it released through its own descriptor and removes the path only while it is still that file, so a lock put back after its release is cleared the same way, with a `cleared a console start lock its holder had released` warning. An `autonomy-precondition` refusal from the bus carries the check that failed (owner notices off, no tested reverse, a resolver without the right, no `review.verdict` holder, no enforced budget, out of the loop only from near the loop, the trail review or near-loop decision missing); the human-transport child writes only the code, so a terminal `org autonomy set` shows the Console's message. `mosaic org template save` writes free text verbatim: it never rewrites prose, and the preview lists every field that mentions the organization id, a project id, an agent name or the owner's account, with the names it mentions, for the owner to edit before sharing. Saving under a name already saved for the account refuses `template-exists` (exit 2, nothing written) with a next line to save under another name or list the saved ones with `mosaic org template list ORG`. The instance processing switch is separate from per-business launches off/on. `mosaic halt` durably stops managed processing across every business in the diff --git a/packages/cli/src/org-console.mjs b/packages/cli/src/org-console.mjs index 75de065b4..4a5eefab9 100644 --- a/packages/cli/src/org-console.mjs +++ b/packages/cli/src/org-console.mjs @@ -125,10 +125,10 @@ function takeLock(ctx, warn = () => {}) { // the path only while the path is still that file. A clear that moved the lock aside and puts it back // after this release puts back a released lock, which the next start or stop clears; and the release // never removes a lock another start took meanwhile. + // A failed marker write (disk full) still removes the path: an empty lock would refuse forever. return () => { try { - ftruncateSync(fd, 0); - writeSync(fd, JSON.stringify({ released: true }), 0); + try { ftruncateSync(fd, 0); writeSync(fd, JSON.stringify({ released: true }), 0); } catch { /* the matched unlink below still runs */ } const own = fstatSync(fd), now = lstatSync(path); if (own.dev === now.dev && own.ino === now.ino) rmSync(path, { force: true }); } catch { /* gone already, or moved aside by a clear */ } finally { closeSync(fd); }