can1357/oh-my-pi · error

Daemon cwd is not a directory: ${spec.cwd}

Error message

Daemon cwd is not a directory: ${spec.cwd}

What it means

DaemonBroker.start stats spec.cwd before spawning and requires it to exist as a directory. If the path does not exist fs.stat throws ENOENT, and if it exists but is not a directory (a file, symlink to a file, etc.) this error is thrown. The broker refuses to launch a daemon with an invalid working directory.

Source

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

		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: {
					name: spec.name,
					id: crypto.randomUUID(),
					state: "starting",
					createdAt: now,
					startedAt: now,
					restartCount: 0,
					outputBytes: 0,
					owner,
					persist: spec.persist,
					detached: spec.detached,
				},
				dir,
				log: await DaemonLog.open(dir),

View on GitHub (pinned to 9690622007)

Solutions

  1. Create the directory first: `mkdir -p <cwd>` (or fs.mkdir(recursive: true)) before starting the daemon.
  2. Fix the cwd in the spec to an absolute path to an existing directory.
  3. Check for typos or stale config pointing at a removed/renamed directory.

Example fix

// before
await broker.start({ name: "worker", cwd: "/srv/app/workers", ... });
// after
import * as fs from "node:fs";
fs.mkdirSync("/srv/app/workers", { recursive: true });
await broker.start({ name: "worker", cwd: "/srv/app/workers", ... });
Defensive patterns

Strategy: validation

Validate before calling

import { statSync } from "node:fs";
const st = statSync(spec.cwd); // throws ENOENT if missing
if (!st.isDirectory()) throw new Error(`cwd is not a directory: ${spec.cwd}`);

Try / catch

try {
  await broker.start(spec);
} catch (err) {
  if (err instanceof Error && (err.message.startsWith("Daemon cwd is not a directory") || (err as NodeJS.ErrnoException).code === "ENOENT")) {
    fs.mkdirSync(spec.cwd, { recursive: true });
    await broker.start(spec);
  } else throw err;
}

Prevention

When it happens

Trigger: Passing a DaemonSpec whose cwd points to a regular file, or whose cwd path does not exist at all (fs.stat rejects with ENOENT which propagates to the caller).

Common situations: Typo'd or relative path in config; the directory was deleted or renamed after config was written; running from a checkout where a build artifact directory hasn't been created yet.

Related errors


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