{"record":{"id":"81554dd3566af691","repo":"can1357/oh-my-pi","slug":"daemon-readypending-must-be-an-array","errorCode":null,"errorMessage":"daemon.readyPending must be an array","messagePattern":"daemon\\.readyPending must be an array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/launch/protocol.ts","lineNumber":226,"sourceCode":"\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\");\n\tif (!log && port === undefined) throw new Error(\"ready requires log or port\");\n\treturn { log, port, host, timeoutMs };\n}\n","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/launch/protocol.ts#L208-L244","documentation":"Thrown by readyPendingList() when the snapshot's readyPending field is not an array. readyPending tracks which readiness conditions ('log'/'port') are still being awaited, and the parser requires it to always be an array, even when empty.","triggerScenarios":"parseDaemonSnapshot receiving a payload where readyPending is undefined, null, an object, or a single string like \"port\" instead of an array.","commonSituations":"Older daemon versions omitting the field entirely; a producer serializing a single readiness condition without wrapping it in a list; JSON schema drift between client and daemon.","solutions":["Have the producer always emit an array for readyPending, e.g. [] when nothing is pending","Wrap single values: \"port\" → [\"port\"]","Align daemon and client versions so the field is always present"],"exampleFix":"// before\nsnapshot = { id: \"d1\", state: \"starting\", readyPending: \"port\" }\n// after\nsnapshot = { id: \"d1\", state: \"starting\", readyPending: [\"port\"] }","handlingStrategy":"validation","validationCode":"function isReadyPending(v: unknown): v is (\"log\" | \"port\")[] {\n  return Array.isArray(v) && v.every((x) => x === \"log\" || x === \"port\");\n}\nif (!isReadyPending(snapshot.readyPending)) {\n  snapshot.readyPending = Array.isArray(snapshot.readyPending) ? snapshot.readyPending : [];\n}","typeGuard":"function isReadyPending(v: unknown): v is (\"log\"|\"port\")[] { return Array.isArray(v) && v.every((x) => x === \"log\" || x === \"port\"); }","tryCatchPattern":"try {\n  const snap = parseDaemonSnapshot(raw);\n} catch (err) {\n  if (err instanceof Error && err.message === \"daemon.readyPending must be an array\") {\n    // treat as legacy snapshot: default readyPending to [] and retry\n  } else throw err;\n}","preventionTips":["Always emit readyPending as an array from the daemon, even when empty","Never serialize a single condition string — wrap it in a list","Keep old daemons from omitting the field: gate the field behind a protocol version","Schema-validate snapshots before parsing"],"tags":["validation","arrays","ipc","protocol"],"backgroundTag":"schema-validation-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}