{"record":{"id":"0d9d0982e71466e8","repo":"can1357/oh-my-pi","slug":"daemon-operation-name-process-is-unavailable","errorCode":null,"errorMessage":"Daemon ${operation.name} process is unavailable","messagePattern":"Daemon (.+?) process is unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/launch/broker.ts","lineNumber":1132,"sourceCode":"\t\t\tthrow new Error(`Daemon ${operation.name} is ${record.snapshot.state}`);\n\t\t}\n\t\tif (operation.data === undefined && operation.signal === undefined) {\n\t\t\tthrow new Error(\"send requires data or signal\");\n\t\t}\n\t\tif (operation.data !== undefined) {\n\t\t\tif (record.pty) record.pty.write(operation.data);\n\t\t\telse if (record.input) {\n\t\t\t\trecord.input.write(operation.data);\n\t\t\t\tawait record.input.flush();\n\t\t\t} else throw new Error(`Daemon ${operation.name} stdin is unavailable`);\n\t\t}\n\t\tif (operation.signal) {\n\t\t\tif (process.platform === \"win32\" && record.pty) {\n\t\t\t\tif (operation.signal === \"SIGINT\") record.pty.write(\"\\u0003\");\n\t\t\t\telse record.pty.kill();\n\t\t\t} else {\n\t\t\t\tconst processRef = record.snapshot.pid === undefined ? null : Process.fromPid(record.snapshot.pid);\n\t\t\t\tif (!processRef) throw new Error(`Daemon ${operation.name} process is unavailable`);\n\t\t\t\tprocessRef.killTree(SIGNAL_NUMBER[operation.signal]);\n\t\t\t}\n\t\t}\n\t\treturn { op: \"send\", daemon: record.snapshot };\n\t}\n\n\tasync #stopRecord(record: ManagedDaemon, timeoutMs: number): Promise<void> {\n\t\tawait this.#refreshDetached(record);\n\t\tif (terminalState(record.snapshot.state)) return;\n\t\trecord.stopRequested = true;\n\t\tif (record.restartTimer) {\n\t\t\tclearTimeout(record.restartTimer);\n\t\t\trecord.restartTimer = undefined;\n\t\t\trecord.snapshot.state = \"exited\";\n\t\t\trecord.snapshot.exitedAt = Date.now();\n\t\t\tthis.#persist(record);\n\t\t\tawait record.log?.close();\n\t\t\trecord.log = undefined;","sourceCodeStart":1114,"sourceCodeEnd":1150,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/launch/broker.ts#L1114-L1150","documentation":"To deliver a signal on non-Windows platforms (or Windows without a PTY), the broker resolves the daemon's recorded pid via Process.fromPid and kills its process tree. If the snapshot has no pid, or the pid could not be resolved to a live process handle, the broker throws this error instead of signaling blindly.","triggerScenarios":"Sending a signal to a daemon whose snapshot lacks a pid (never observed or lost across broker restart with a detached record) or whose pid is no longer resolvable as a process handle.","commonSituations":"Daemon already reaped but state not yet refreshed; broker restarted and reattached to a detached daemon whose pid record is missing; pid reuse/exit raced with the signal send.","solutions":["Refresh the daemon state (list/get) and confirm the pid before sending signals.","Use the broker's stop operation instead of a raw signal if you just want the daemon to terminate.","If the daemon is already gone, treat this as already-stopped: clear the record and move on."],"exampleFix":"// before\nawait broker.send({ name: \"worker\", signal: \"SIGTERM\" }); // pid missing -> throws\n// after\nconst snap = await broker.get(\"worker\");\nif (snap?.pid !== undefined) {\n  await broker.send({ name: \"worker\", signal: \"SIGTERM\" });\n} else {\n  await broker.stop({ name: \"worker\" });\n}","handlingStrategy":"validation","validationCode":"const snap = await broker.get(name);\nif (!snap || snap.pid === undefined) throw new Error(`${name} has no pid; use stop instead of signals`);","typeGuard":"function hasPid(s: { pid?: number }): s is { pid: number } {\n  return typeof s.pid === \"number\";\n}","tryCatchPattern":"try {\n  await broker.send({ name, signal: \"SIGTERM\" });\n} catch (err) {\n  if (err instanceof Error && err.message.endsWith(\"process is unavailable\")) {\n    // treat as already-stopped or fall back to broker.stop\n    await broker.stop({ name });\n  } else throw err;\n}","preventionTips":["Prefer the broker's stop operation over raw signal sends for termination.","Refresh the snapshot (get/list) before signaling detached daemons.","Handle broker restarts: re-attach and verify pid presence before signaling."],"tags":["daemon","signal","process"],"backgroundTag":"process-not-found","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}