can1357/oh-my-pi · error
Daemon ${spec.name} is already ${existing.snapshot.state}
Error message
Daemon ${spec.name} is already ${existing.snapshot.state} What it means
When #start finds an existing record for the daemon name, it refreshes detached state and checks terminalState(existing.snapshot.state). If the daemon is not in a terminal state (still running/stopping/etc.), starting it again would double-spawn, so the broker throws 'Daemon <name> is already <state>'. A sibling check also blocks restarts with unacknowledged pendingCompletions.
Source
Thrown at packages/coding-agent/src/launch/broker.ts:619
throw new Error("A detached daemon cannot allocate a PTY");
}
if (
spec.pty &&
process.platform === "win32" &&
[".bat", ".cmd"].includes(path.extname(spec.application).toLowerCase())
) {
throw new Error('Windows batch files require application "cmd.exe" with the batch path after "/c"');
}
if (this.#startingNames.has(spec.name)) {
throw new Error(`Daemon ${spec.name} is already starting`);
}
this.#startingNames.add(spec.name);
let record: ManagedDaemon;
try {
const existing = this.#records.get(spec.name);
if (existing) await this.#refreshDetached(existing);
if (existing && !terminalState(existing.snapshot.state)) {
throw new Error(`Daemon ${spec.name} is already ${existing.snapshot.state}`);
}
if (existing && existing.pendingCompletions.length > 0) {
throw new Error(`Daemon ${spec.name} has unacknowledged completion notifications`);
}
if (spec.ready?.log) {
try {
new RegExp(spec.ready.log, "u");
} catch (error) {
throw new Error(`Invalid readiness regex: ${error instanceof Error ? error.message : String(error)}`);
}
}
const stat = await fs.stat(spec.cwd);
if (!stat.isDirectory()) throw new Error(`Daemon cwd is not a directory: ${spec.cwd}`);
const dir = path.join(this.#runtimeDir, "daemons", spec.name);
const now = Date.now();
record = {
spec,
snapshot: {View on GitHub (pinned to 9690622007)
Solutions
- Check the daemon's current state first; only start when it is absent or in a terminal state (stopped/exited/crashed)
- If the daemon is running and needs a fresh instance, explicitly stop it, wait for a terminal state, then start
- If it is stuck mid-shutdown, wait or force-kill before restarting
- Acknowledge pending completion notifications if the sibling 'unacknowledged completion notifications' error blocks you
Example fix
// before await broker.start(spec); // throws: Daemon dev is already running // after const snap = await broker.status(spec.name); if (snap && !isTerminalState(snap.state)) await broker.stop(spec.name); await broker.start(spec);
Defensive patterns
Strategy: validation
Validate before calling
const TERMINAL_STATES = new Set(['stopped', 'exited', 'crashed', 'failed']);
async function canStart(name: string): Promise<boolean> {
const snap = await broker.status(name);
return !snap || TERMINAL_STATES.has(snap.state);
}
// only call broker.start(spec) when await canStart(spec.name) Type guard
function isTerminalState(state: string): boolean {
return ['stopped', 'exited', 'crashed', 'failed'].includes(state);
} Try / catch
try {
await broker.start(spec);
} catch (err) {
const m = /^Daemon (.+) is already (.+)$/.exec(err instanceof Error ? err.message : '');
if (m) {
await broker.stop(m[1]); // or reuse the running daemon
await broker.start(spec);
return;
}
throw err;
} Prevention
- Always fetch current daemon state and branch on it before issuing start
- Treat start as non-idempotent: build ensure-running logic on top of status + conditional start
- Acknowledge pending completion notifications promptly so they don't block restarts
- If a daemon seems hung in a non-terminal state, stop/force-kill explicitly rather than calling start again
When it happens
Trigger: Calling the start RPC for a name whose existing record's snapshot.state is non-terminal — e.g. daemon already 'running' or mid-shutdown — typically a start-again request for an already-launched daemon.
Common situations: Scripts idempotently calling start without checking current state; user re-running a launch command in another terminal; a daemon that appears hung being 'started again' rather than restarted; stale client view after another client already started the daemon.
Related errors
- Debug session ${root.id} is still active. Terminate it befor
- Daemon ${spec.name} is already starting
- Daemon ${spec.name} has unacknowledged completion notificati
- Daemon ${operation.name} is ${record.snapshot.state}
- Daemon broker client is closed
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/829de9f3b9ca7886.
Report an issue: GitHub.