can1357/oh-my-pi · error

Daemon ${operation.name} generation ${boundGeneration} exite

Error message

Daemon ${operation.name} generation ${boundGeneration} exited${exit}; the wait was rejected instead of continuing against a replacement generation

What it means

When a wait operation is in progress and the specific daemon generation being waited on exits (crashes or terminates), the broker rejects the wait instead of silently transferring it to a replacement generation started under the same name. The message includes the generation number and, if known, the exit code. This prevents a caller from waiting on stale readiness state of a daemon process it did not launch.

Source

Thrown at packages/coding-agent/src/launch/broker.ts:1098

		const generationEnded = (): boolean =>
			record.generation !== boundGeneration || record.snapshot.state === "restarting";
		const condition = (): boolean => {
			if (generationEnded()) return true;
			if (pattern) {
				const match = pattern.exec(record.readinessBuffer);
				if (!match) return false;
				matched = match[0].slice(0, 500);
				return true;
			}
			if (operation.for === "exit") return terminalState(record.snapshot.state);
			// Wake on observed readiness or any terminal state so the wait never
			// blocks for the full timeout; success is judged by readyObserved below.
			return readyObserved() || terminalState(record.snapshot.state);
		};
		const woke = condition() || (await this.#waitUntil(record, condition, operation.timeoutMs));
		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) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Inspect daemon logs/snapshot to find why the generation exited and fix the underlying crash before retrying.
  2. Restart the daemon and re-issue the wait against the new generation.
  3. Check exitCode in the error/snapshot — a signal death suggests external kill (OOM, supervisor), a nonzero code suggests an app error.

Example fix

// before
await broker.wait({ name: "api", for: "ready", generation: 3, timeoutMs: 30000 }); // generation 3 died
// after
try {
  await broker.wait({ name: "api", for: "ready", generation: 3, timeoutMs: 30000 });
} catch (err) {
  // generation ended: restart then wait on the fresh generation
  await broker.start({ name: "api", ...spec });
  await broker.wait({ name: "api", for: "ready", timeoutMs: 30000 });
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Cannot be pre-validated: the exit happens asynchronously during the wait.
// Optionally check current state first:
const snap = await broker.get(name);
if (snap && ["exited", "failed", "crashed"].includes(snap.state)) throw new Error(`${name} already dead`);

Try / catch

try {
  await broker.wait({ name, for: "ready", generation, timeoutMs });
} catch (err) {
  if (err instanceof Error && err.message.includes("exited") && err.message.includes("replacement generation")) {
    // inspect logs/exitCode, fix root cause, restart and re-wait
  } else throw err;
}

Prevention

When it happens

Trigger: Calling wait with a bound generation while that daemon process exits before the condition is met or the timeout elapses — e.g. the daemon crashes mid-wait, or is killed externally during the wait window.

Common situations: Daemon crashed due to a bad startup config; supervisor or OOM killer terminated the process; someone restarted the daemon while a readiness wait was outstanding.

Related errors


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