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
- Inspect daemon logs/snapshot to find why the generation exited and fix the underlying crash before retrying.
- Restart the daemon and re-issue the wait against the new generation.
- 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
- Ensure the daemon is healthy (valid config, dependencies present) before issuing long waits.
- React to the exit by restarting the daemon and waiting on the new generation, never by retrying the stale generation.
- Record exitCode from the error to distinguish crashes from external kills (OOM/supervisor).
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
- cli_message(command, exit_code, stdout, stderr)
- blob daemon ${input} responded ${response.status}
- ${argv[0]} exited with code ${exitCode} before reporting a t
- ssh reverse forward to ${config.sshTarget} exited with code
- blob broker exposure failed to start
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/f369ee704e2e1757.
Report an issue: GitHub.