can1357/oh-my-pi · error
Daemon ${operation.name} is ${record.snapshot.state}
Error message
Daemon ${operation.name} is ${record.snapshot.state} What it means
DaemonBroker's send path (#send) refreshes the daemon snapshot and refuses to deliver data or signals to a daemon that is in a terminal state (exited/failed etc.) or currently stopping. Sending would be a no-op or write into a dead pipe, so the broker throws with the daemon's current state in the message.
Source
Thrown at packages/coding-agent/src/launch/broker.ts:1114
if (generationEnded()) {
const exit = record.snapshot.exitCode === undefined ? "" : ` with exit code ${record.snapshot.exitCode}`;
throw new Error(
`Daemon ${operation.name} generation ${boundGeneration} exited${exit}; ` +
"the wait was rejected instead of continuing against a replacement generation",
);
}
// A for:"ready" wait that woke on a terminal exit without ever observing
// readiness is still "not ready" — surface it as timed out so callers and the
// renderer don't chain work against a dead process.
const timedOut = operation.for === "ready" && !pattern ? !readyObserved() : !woke;
return { op: "wait", daemon: record.snapshot, matched, timedOut };
}
async #send(operation: Extract<DaemonOperation, { op: "send" }>): Promise<DaemonRpcResult> {
const record = this.#record(operation.name);
await this.#refreshDetached(record);
if (terminalState(record.snapshot.state) || record.snapshot.state === "stopping") {
throw new Error(`Daemon ${operation.name} is ${record.snapshot.state}`);
}
if (operation.data === undefined && operation.signal === undefined) {
throw new Error("send requires data or signal");
}
if (operation.data !== undefined) {
if (record.pty) record.pty.write(operation.data);
else if (record.input) {
record.input.write(operation.data);
await record.input.flush();
} else throw new Error(`Daemon ${operation.name} stdin is unavailable`);
}
if (operation.signal) {
if (process.platform === "win32" && record.pty) {
if (operation.signal === "SIGINT") record.pty.write("\u0003");
else record.pty.kill();
} else {
const processRef = record.snapshot.pid === undefined ? null : Process.fromPid(record.snapshot.pid);
if (!processRef) throw new Error(`Daemon ${operation.name} process is unavailable`);View on GitHub (pinned to 9690622007)
Solutions
- Check the daemon snapshot state (list/get) before sending; skip or queue sends for non-running daemons.
- Restart the daemon if input must be delivered, then re-send.
- Remove the stale stop/send sequence from the caller so it doesn't write after issuing stop.
Example fix
// before
await broker.stop({ name: "worker" });
await broker.send({ name: "worker", data: "flush\n" }); // throws: daemon is stopping
// after
await broker.send({ name: "worker", data: "flush\n" });
await broker.stop({ name: "worker" }); Defensive patterns
Strategy: validation
Validate before calling
const snap = await broker.get(name);
const dead = !snap || snap.state === "stopping" || ["exited", "failed", "crashed", "stopped"].includes(snap.state);
if (dead) throw new Error(`skip send: ${name} is ${snap?.state ?? "unknown"}`); Type guard
function isSendableState(s: { state: string }): boolean {
return s.state === "running" || s.state === "starting";
} Try / catch
try {
await broker.send({ name, data });
} catch (err) {
if (err instanceof Error && /^Daemon .* is (exited|failed|stopping|stopped)/.test(err.message)) {
// daemon dead/stopping: restart if more input must be delivered
} else throw err;
} Prevention
- Order operations so all sends happen before stop.
- Track daemon lifecycle in the client and stop sending once a stop was requested.
- Refresh snapshots before sends in long-running supervisors to avoid stale state.
When it happens
Trigger: Calling a send operation ({ op: "send", name, data|signal }) while the named daemon is exited, crashed, or in the middle of stopping.
Common situations: Client raced with daemon shutdown; a previous stop command was issued but the client kept sending input; daemon crashed earlier and the client's cache was stale.
Related errors
- blob daemon ${input} responded ${response.status}
- Daemon broker authentication failed
- Daemon ${spec.name} is already starting
- Daemon ${spec.name} is already ${existing.snapshot.state}
- Daemon ${spec.name} has unacknowledged completion notificati
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/082cbbbb184d9f41.
Report an issue: GitHub.