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

  1. Check the daemon snapshot state (list/get) before sending; skip or queue sends for non-running daemons.
  2. Restart the daemon if input must be delivered, then re-send.
  3. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/082cbbbb184d9f41. Report an issue: GitHub.