diff --git a/bin/ccam.js b/bin/ccam.js index b7426be2..1c01e94c 100755 --- a/bin/ccam.js +++ b/bin/ccam.js @@ -1454,7 +1454,10 @@ async function cmdService(sub, flags) { console.error(c.red("✖ client/dist is missing — build it first: npm run build")); process.exit(1); } - const env = { NODE_ENV: "production" }; + // systemd --user units get systemd's own minimal PATH, not the shell's — + // without forwarding it, the server can't find `claude`/`codex` when + // spawning lanes even though they're on the installer's PATH. + const env = { NODE_ENV: "production", PATH: process.env.PATH || "" }; if (flags.port) env.DASHBOARD_PORT = String(flags.port); const result = svc.installUserService({ name, @@ -1834,12 +1837,18 @@ async function cmdLanesLifecycle(action, args) { * ALIAS — which is most of them, ship-feature's skill says `tests`, `booting`, * `ship`, `gate-blocked` — is not a node id, so indexOf answers -1 and every * detection, however far behind, reads as leading. + * + * `>=`, not `>`: a lane that has never declared anything is RENDERED at its + * detected node by the server (`inferredDisplayStage`), so detection and the + * `current` node are the same one — and for that lane this badge is the only + * readout of what it is doing. A declared lane can never tie, because + * recordDetection refuses a detection that isn't strictly ahead of it. */ function detectionLeadsDeclaration(l) { if (!l.detected_stage) return false; const nodes = l.pipeline_nodes || []; return ( - nodes.findIndex((n) => n.id === l.detected_stage) > + nodes.findIndex((n) => n.id === l.detected_stage) >= nodes.findIndex((n) => n.state === "current") ); } diff --git a/scripts/plugin-bootstrap.js b/scripts/plugin-bootstrap.js index 8b001673..4a5f7f78 100644 --- a/scripts/plugin-bootstrap.js +++ b/scripts/plugin-bootstrap.js @@ -480,6 +480,9 @@ function bootstrap(rt = runtimeDir(), { force = false } = {}) { NODE_ENV: "production", NODE_PATH: path.join(rt, "node_modules"), DASHBOARD_CLIENT_DIST: path.join(rt, "client-dist"), + // systemd --user units get systemd's own minimal PATH, not the + // shell's — forward it so the server can find `claude`/`codex`. + PATH: process.env.PATH || "", }, }) : "unavailable";