fix(service): forward PATH into the ccam systemd --user unit

systemd --user units start with systemd's own minimal PATH, not the
installer's shell PATH, so the dashboard couldn't find claude/codex
(e.g. installed under ~/.local/bin) when spawning a lane from a
service-managed server. Forward process.env.PATH into the unit's
environment from both ccam service install and the plugin bootstrap's
auto-install path.
This commit is contained in:
2026-09-03 11:29:24 +07:00
parent 8711917d44
commit 13b2bc4ab6
2 changed files with 14 additions and 2 deletions
+11 -2
View File
@@ -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")
);
}