{"record":{"id":"4300a179703d59b7","repo":"can1357/oh-my-pi","slug":"daemon-broker-environment-is-incomplete","errorCode":null,"errorMessage":"Daemon broker environment is incomplete","messagePattern":"Daemon broker environment is incomplete","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"packages/coding-agent/src/launch/broker.ts","lineNumber":1385,"sourceCode":"\t\t\t\t\tthis.#scheduleIdleShutdown();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this.#clients.size === 0) await this.shutdown();\n\t\t\t})();\n\t\t}, this.#idleGraceMs);\n\t}\n}\n\nexport interface DaemonBrokerStartOptions {\n\t/** Base of the exponential child-restart backoff. */\n\trestartBackoffBaseMs?: number;\n}\n\n/** Start the detached project or global daemon broker selected by the CLI worker host. */\nexport async function startDaemonBrokerFromEnvironment(options: DaemonBrokerStartOptions = {}): Promise<void> {\n\tconst projectDir = process.env[DAEMON_PROJECT_DIR_ENV];\n\tconst runtimeDir = process.env[DAEMON_RUNTIME_DIR_ENV];\n\tif (!projectDir || !runtimeDir) throw new Error(\"Daemon broker environment is incomplete\");\n\tdelete process.env[DAEMON_PROJECT_DIR_ENV];\n\tdelete process.env[DAEMON_RUNTIME_DIR_ENV];\n\tconst rawGrace = process.env[DAEMON_IDLE_GRACE_ENV];\n\tdelete process.env[DAEMON_IDLE_GRACE_ENV];\n\tconst parsedGrace = rawGrace === undefined ? DEFAULT_IDLE_GRACE_MS : Number.parseInt(rawGrace, 10);\n\tconst idleGraceMs = Number.isFinite(parsedGrace) && parsedGrace >= 0 ? parsedGrace : DEFAULT_IDLE_GRACE_MS;\n\tconst requestedRestartBackoffBaseMs = options.restartBackoffBaseMs ?? RESTART_BACKOFF_BASE_MS;\n\tconst restartBackoffBaseMs =\n\t\tNumber.isFinite(requestedRestartBackoffBaseMs) && requestedRestartBackoffBaseMs >= 0\n\t\t\t? requestedRestartBackoffBaseMs\n\t\t\t: RESTART_BACKOFF_BASE_MS;\n\tawait fs.mkdir(runtimeDir, { recursive: true, mode: 0o700 });\n\tconst lease = await acquireBrokerLease(runtimeDir);\n\tif (!lease) return;\n\tsetProcessName(\"omp daemon broker\");\n\t// Record the scope's project dir so `omp ps` can map this hash-keyed runtime\n\t// dir back to its project (and derive the Windows pipe name) offline.\n\tvoid writeDaemonScopeMeta(runtimeDir, projectDir).catch(error => {","sourceCodeStart":1367,"sourceCodeEnd":1403,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/launch/broker.ts#L1367-L1403","documentation":"startDaemonBrokerFromEnvironment runs inside the spawned daemon broker worker process and reads DAEMON_PROJECT_DIR_ENV and DAEMON_RUNTIME_DIR_ENV from the environment. If either is missing or empty, the broker cannot know which project to serve or where to keep runtime state, so it throws immediately at startup.","triggerScenarios":"Spawning the broker worker without setting both DAEMON_PROJECT_DIR_ENV and DAEMON_RUNTIME_DIR_ENV env vars; env vars set to empty strings; launching the worker entry manually without going through DaemonBrokerClient spawn plumbing.","commonSituations":"Custom process managers (systemd, docker) dropping env vars; running the worker entry directly in a REPL or test; stale spawn code written before the env overlay was added.","solutions":["Ensure the spawn site passes the env overlay containing DAEMON_PROJECT_DIR_ENV and DAEMON_RUNTIME_DIR_ENV (see #spawnBroker)","Launch the broker via the library's worker spawn path instead of starting the worker module manually","Log/verify the child process env at spawn time to confirm both vars are present"],"exampleFix":"// before\nBun.spawn([workerCmd], { env: { ...process.env } });\n// after\nBun.spawn([workerCmd], { env: { ...process.env, [DAEMON_PROJECT_DIR_ENV]: projectDir, [DAEMON_RUNTIME_DIR_ENV]: runtimeDir } });","handlingStrategy":"validation","validationCode":"const projectDir = process.env.DAEMON_PROJECT_DIR;\nconst runtimeDir = process.env.DAEMON_RUNTIME_DIR;\nif (!projectDir || !runtimeDir) throw new Error('refusing to spawn broker worker: missing DAEMON_PROJECT_DIR/DAEMON_RUNTIME_DIR');","typeGuard":null,"tryCatchPattern":"try {\n  await startDaemonBrokerFromEnvironment();\n} catch (err) {\n  if (err.message.includes('environment is incomplete')) {\n    logger.error('broker worker spawned without required env vars', { missing: ['DAEMON_PROJECT_DIR','DAEMON_RUNTIME_DIR'].filter(k => !process.env[k]) });\n  }\n  throw err;\n}","preventionTips":["Always spawn the broker via the library's worker spawn path (#spawnBroker) which sets the env overlay","Never launch the worker entry module directly in scripts/tests without replicating the env","Verify env propagation when running under process managers or containers"],"tags":["ipc","environment","configuration","daemon"],"backgroundTag":"missing-env-var","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}