can1357/oh-my-pi · error

Unknown daemon signal

Error message

Unknown daemon signal: ${signal}

What it means

Thrown by daemonSignal() when a daemon operation's signal field is a string but not one of the supported POSIX signals (SIGINT, SIGTERM, SIGHUP, SIGQUIT, SIGKILL). The daemon forwards only these validated signals to the child process.

Solutions

  1. Use the full uppercase names: SIGINT, SIGTERM, SIGHUP, SIGQUIT, SIGKILL
  2. Convert short/numeric names at the producer (e.g. map "9"→"SIGKILL", "TERM"→"SIGTERM")
  3. Drop unsupported signals; pick the closest supported one (e.g. SIGKILL instead of SIGSTOP)

Example fix

// before
operation = { type: "signal", id: "d1", signal: "TERM" }
// after
operation = { type: "signal", id: "d1", signal: "SIGTERM" }
Defensive patterns

Strategy: validation

Validate before calling

const SIGNALS = ["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGKILL"] as const;
type AllowedSignal = typeof SIGNALS[number];
function isAllowedSignal(v: unknown): v is AllowedSignal {
  return typeof v === "string" && (SIGNALS as readonly string[]).includes(v);
}
if (!isAllowedSignal(op.signal)) throw new Error(`unsupported signal: ${op.signal}`);

Type guard

function isAllowedSignal(v: unknown): v is "SIGINT"|"SIGTERM"|"SIGHUP"|"SIGQUIT"|"SIGKILL" { return typeof v === "string" && ["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGKILL"].includes(v); }

Try / catch

try {
  const op = parseDaemonOperation(raw);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("Unknown daemon signal")) {
    // normalize short names (TERM→SIGTERM) or numeric ids (9→SIGKILL) and retry
  } else throw err;
}

Prevention

When it happens

Trigger: parseDaemonOperation receiving a signal operation with signal: "sigterm" (lowercase), "SIGUSR1" (unsupported), "TERM" (abbreviated), "9" (numeric), or a non-string value.

Common situations: Scripts using short signal names (TERM, KILL) or numeric IDs as accepted by kill(1); attempts to send signals the daemon intentionally does not support (SIGUSR1/2, SIGSTOP); lowercase signal names from cross-platform code.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at packages/coding-agent/src/launch/protocol.ts:222

function daemonState(value: unknown): DaemonState {
	const state = stringValue(value, "daemon state");
	if (state === "starting" || state === "running" || state === "ready" || state === "restarting") return state;
	if (state === "stopping" || state === "exited" || state === "failed") return state;
	throw new Error(`Unknown daemon state: ${state}`);
}

function restartPolicy(value: unknown): DaemonRestartPolicy {
	const policy = stringValue(value, "restart policy");
	if (policy === "no" || policy === "on-failure" || policy === "always") return policy;
	throw new Error(`Unknown restart policy: ${policy}`);
}

function daemonSignal(value: unknown): DaemonSignal {
	const signal = stringValue(value, "signal");
	if (signal === "SIGINT" || signal === "SIGTERM" || signal === "SIGHUP") return signal;
	if (signal === "SIGQUIT" || signal === "SIGKILL") return signal;
	throw new Error(`Unknown daemon signal: ${signal}`);
}

function readyPendingList(value: unknown): ("log" | "port")[] {
	if (!Array.isArray(value)) throw new Error("daemon.readyPending must be an array");
	const result: ("log" | "port")[] = [];
	for (const item of value) {
		if (item !== "log" && item !== "port") throw new Error(`Unknown readiness condition: ${String(item)}`);
		result.push(item);
	}
	return result;
}

function readySpec(value: unknown): DaemonReadySpec {
	const source = record(value, "ready");
	const log = optionalString(source.log, "ready.log");
	const port = optionalNumber(source.port, "ready.port");
	const host = optionalString(source.host, "ready.host");
	const timeoutMs = numberValue(source.timeoutMs, "ready.timeoutMs");

View on GitHub (pinned to 9690622007)