{"record":{"id":"6dcab51d20a125e5","repo":"can1357/oh-my-pi","slug":"daemon-spec-name-has-unacknowledged-completion","errorCode":null,"errorMessage":"Daemon ${spec.name} has unacknowledged completion notifications","messagePattern":"Daemon (.+?) has unacknowledged completion notifications","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/launch/broker.ts","lineNumber":622,"sourceCode":"\t\t\tspec.pty &&\n\t\t\tprocess.platform === \"win32\" &&\n\t\t\t[\".bat\", \".cmd\"].includes(path.extname(spec.application).toLowerCase())\n\t\t) {\n\t\t\tthrow new Error('Windows batch files require application \"cmd.exe\" with the batch path after \"/c\"');\n\t\t}\n\t\tif (this.#startingNames.has(spec.name)) {\n\t\t\tthrow new Error(`Daemon ${spec.name} is already starting`);\n\t\t}\n\t\tthis.#startingNames.add(spec.name);\n\t\tlet record: ManagedDaemon;\n\t\ttry {\n\t\t\tconst existing = this.#records.get(spec.name);\n\t\t\tif (existing) await this.#refreshDetached(existing);\n\t\t\tif (existing && !terminalState(existing.snapshot.state)) {\n\t\t\t\tthrow new Error(`Daemon ${spec.name} is already ${existing.snapshot.state}`);\n\t\t\t}\n\t\t\tif (existing && existing.pendingCompletions.length > 0) {\n\t\t\t\tthrow new Error(`Daemon ${spec.name} has unacknowledged completion notifications`);\n\t\t\t}\n\t\t\tif (spec.ready?.log) {\n\t\t\t\ttry {\n\t\t\t\t\tnew RegExp(spec.ready.log, \"u\");\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow new Error(`Invalid readiness regex: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst stat = await fs.stat(spec.cwd);\n\t\t\tif (!stat.isDirectory()) throw new Error(`Daemon cwd is not a directory: ${spec.cwd}`);\n\t\t\tconst dir = path.join(this.#runtimeDir, \"daemons\", spec.name);\n\t\t\tconst now = Date.now();\n\t\t\trecord = {\n\t\t\t\tspec,\n\t\t\t\tsnapshot: {\n\t\t\t\t\tname: spec.name,\n\t\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\t\tstate: \"starting\",","sourceCodeStart":604,"sourceCodeEnd":640,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/launch/broker.ts#L604-L640","documentation":"DaemonBroker.start throws this when a daemon with the same name was previously launched and exited, but its completion notifications have not been acknowledged yet. The broker keeps pending completions per record so callers can observe daemon termination; starting a new daemon under the same name before acknowledging would silently discard that record. The caller must acknowledge completions (or remove the daemon record) before reusing the name.","triggerScenarios":"Calling broker.start(spec) for a daemon name whose existing record has pendingCompletions.length > 0 — i.e. the previous daemon under that name terminated and its exit notification was never fetched/acknowledged.","commonSituations":"A daemon crashed or exited and the client never polled for or acknowledged the completion event, then the operator re-runs the launch command with the same daemon name. Also happens after restarting a supervisor client that lost track of prior notifications.","solutions":["Acknowledge the pending completion notifications for that daemon name (via the broker's completion/notification acknowledge API or list operation) before calling start again.","Remove or stop the stale daemon record so pendingCompletions are cleared, then start the daemon.","Use a different daemon name for the new launch if the old notifications must be preserved."],"exampleFix":"// before\nawait broker.start({ name: \"build\", ... });\n// after\nawait broker.acknowledgeCompletions(\"build\"); // drain pending exit notifications\nawait broker.start({ name: \"build\", ... });","handlingStrategy":"validation","validationCode":"const rec = await broker.get?.(spec.name);\nif (rec && (rec.pendingCompletions?.length ?? 0) > 0) {\n  await broker.acknowledgeCompletions(spec.name); // or use another name\n}\nawait broker.start(spec);","typeGuard":null,"tryCatchPattern":"try {\n  await broker.start(spec);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"unacknowledged completion notifications\")) {\n    await broker.acknowledgeCompletions(spec.name);\n    await broker.start(spec);\n  } else throw err;\n}","preventionTips":["Always poll/acknowledge completion notifications after a daemon exits, before reusing its name.","Wrap start calls in a helper that drains pendingCompletions for the name first.","Use unique daemon names per logical run to avoid record reuse entirely."],"tags":["daemon","state-conflict","notifications"],"backgroundTag":"daemon-unacknowledged-notifications","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}