{"record":{"id":"3e9053c117927762","repo":"can1357/oh-my-pi","slug":"unknown-daemon-signal-signal","errorCode":null,"errorMessage":"Unknown daemon signal: ${signal}","messagePattern":"Unknown daemon signal: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/launch/protocol.ts","lineNumber":222,"sourceCode":"\nfunction daemonState(value: unknown): DaemonState {\n\tconst state = stringValue(value, \"daemon state\");\n\tif (state === \"starting\" || state === \"running\" || state === \"ready\" || state === \"restarting\") return state;\n\tif (state === \"stopping\" || state === \"exited\" || state === \"failed\") return state;\n\tthrow new Error(`Unknown daemon state: ${state}`);\n}\n\nfunction restartPolicy(value: unknown): DaemonRestartPolicy {\n\tconst policy = stringValue(value, \"restart policy\");\n\tif (policy === \"no\" || policy === \"on-failure\" || policy === \"always\") return policy;\n\tthrow new Error(`Unknown restart policy: ${policy}`);\n}\n\nfunction daemonSignal(value: unknown): DaemonSignal {\n\tconst signal = stringValue(value, \"signal\");\n\tif (signal === \"SIGINT\" || signal === \"SIGTERM\" || signal === \"SIGHUP\") return signal;\n\tif (signal === \"SIGQUIT\" || signal === \"SIGKILL\") return signal;\n\tthrow new Error(`Unknown daemon signal: ${signal}`);\n}\n\nfunction readyPendingList(value: unknown): (\"log\" | \"port\")[] {\n\tif (!Array.isArray(value)) throw new Error(\"daemon.readyPending must be an array\");\n\tconst result: (\"log\" | \"port\")[] = [];\n\tfor (const item of value) {\n\t\tif (item !== \"log\" && item !== \"port\") throw new Error(`Unknown readiness condition: ${String(item)}`);\n\t\tresult.push(item);\n\t}\n\treturn result;\n}\n\nfunction readySpec(value: unknown): DaemonReadySpec {\n\tconst source = record(value, \"ready\");\n\tconst log = optionalString(source.log, \"ready.log\");\n\tconst port = optionalNumber(source.port, \"ready.port\");\n\tconst host = optionalString(source.host, \"ready.host\");\n\tconst timeoutMs = numberValue(source.timeoutMs, \"ready.timeoutMs\");","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/launch/protocol.ts#L204-L240","documentation":"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.","triggerScenarios":"parseDaemonOperation receiving a signal operation with signal: \"sigterm\" (lowercase), \"SIGUSR1\" (unsupported), \"TERM\" (abbreviated), \"9\" (numeric), or a non-string value.","commonSituations":"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.","solutions":["Use the full uppercase names: SIGINT, SIGTERM, SIGHUP, SIGQUIT, SIGKILL","Convert short/numeric names at the producer (e.g. map \"9\"→\"SIGKILL\", \"TERM\"→\"SIGTERM\")","Drop unsupported signals; pick the closest supported one (e.g. SIGKILL instead of SIGSTOP)"],"exampleFix":"// before\noperation = { type: \"signal\", id: \"d1\", signal: \"TERM\" }\n// after\noperation = { type: \"signal\", id: \"d1\", signal: \"SIGTERM\" }","handlingStrategy":"validation","validationCode":"const SIGNALS = [\"SIGINT\",\"SIGTERM\",\"SIGHUP\",\"SIGQUIT\",\"SIGKILL\"] as const;\ntype AllowedSignal = typeof SIGNALS[number];\nfunction isAllowedSignal(v: unknown): v is AllowedSignal {\n  return typeof v === \"string\" && (SIGNALS as readonly string[]).includes(v);\n}\nif (!isAllowedSignal(op.signal)) throw new Error(`unsupported signal: ${op.signal}`);","typeGuard":"function isAllowedSignal(v: unknown): v is \"SIGINT\"|\"SIGTERM\"|\"SIGHUP\"|\"SIGQUIT\"|\"SIGKILL\" { return typeof v === \"string\" && [\"SIGINT\",\"SIGTERM\",\"SIGHUP\",\"SIGQUIT\",\"SIGKILL\"].includes(v); }","tryCatchPattern":"try {\n  const op = parseDaemonOperation(raw);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Unknown daemon signal\")) {\n    // normalize short names (TERM→SIGTERM) or numeric ids (9→SIGKILL) and retry\n  } else throw err;\n}","preventionTips":["Use full uppercase POSIX names, not kill(1)-style abbreviations or numbers","Whitelist signals in the UI/CLI so unsupported ones cannot be entered","Reject or map signals like SIGUSR1/SIGSTOP before sending operations","Share the signal union type between the daemon and all clients"],"tags":["validation","enum","signals","ipc"],"backgroundTag":"invalid-enum-value","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}