{"record":{"id":"e4e3730c542b57e4","repo":"can1357/oh-my-pi","slug":"ready-requires-log-or-port","errorCode":null,"errorMessage":"ready requires log or port","messagePattern":"ready requires log or port","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/launch/protocol.ts","lineNumber":241,"sourceCode":"}\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\n/** Decode and validate a daemon launch specification. */\nexport function parseDaemonSpec(value: unknown): DaemonSpec {\n\tconst source = record(value, \"daemon spec\");\n\tconst detached = source.detached === undefined ? false : booleanValue(source.detached, \"spec.detached\");\n\treturn {\n\t\tname: stringValue(source.name, \"spec.name\"),\n\t\tapplication: stringValue(source.application, \"spec.application\"),\n\t\targs: stringArray(source.args, \"spec.args\"),\n\t\tenv: stringRecord(source.env, \"spec.env\"),\n\t\tcwd: stringValue(source.cwd, \"spec.cwd\"),\n\t\tpty: booleanValue(source.pty, \"spec.pty\"),\n\t\tready: source.ready === undefined ? undefined : readySpec(source.ready),\n\t\trestart: restartPolicy(source.restart),\n\t\tpersist: booleanValue(source.persist, \"spec.persist\") || detached,\n\t\tdetached,","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/launch/protocol.ts#L223-L259","documentation":"Thrown by readySpec() when a daemon spec's ready section defines a timeout (required) but neither a log pattern nor a port. Readiness detection works by matching log output or probing a port; a ready clause with neither would never complete, so the parser rejects it up front.","triggerScenarios":"parseDaemonSpec receiving ready: { timeoutMs: 5000 } with no log and no port; ready: { host: \"localhost\", timeoutMs: 5000 } (host alone doesn't count); log present but empty string \"\" combined with no port (empty string is falsy).","commonSituations":"Config where the log pattern was deleted or renamed during editing; setting only host/timeout and expecting defaults; empty-string log values that pass type checks but fail the presence check.","solutions":["Add either a log pattern (ready.log) or a port (ready.port) to the ready section","Remove the ready section entirely if no readiness check is needed","If the log pattern became empty, restore it or switch to a port probe","Keep timeoutMs present and finite — it is mandatory in this section"],"exampleFix":"// before\nparseDaemonSpec({ id: \"d1\", command: [\"srv\"], ready: { timeoutMs: 5000 } })\n// after\nparseDaemonSpec({ id: \"d1\", command: [\"srv\"], ready: { port: 8080, timeoutMs: 5000 } })","handlingStrategy":"validation","validationCode":"interface ReadyInput { log?: string; port?: number; host?: string; timeoutMs: number }\nfunction hasReadyTarget(r: ReadyInput): boolean {\n  return Boolean(r.log) || typeof r.port === \"number\";\n}\nif (!hasReadyTarget(ready)) throw new Error(\"ready requires log or port\");","typeGuard":"function hasReadyTarget(r: { log?: string; port?: number }): boolean { return Boolean(r.log) || typeof r.port === \"number\"; }","tryCatchPattern":"try {\n  const spec = parseDaemonSpec(raw);\n} catch (err) {\n  if (err instanceof Error && err.message === \"ready requires log or port\") {\n    // drop the ready section or add a log/port condition before retrying\n  } else throw err;\n}","preventionTips":["Always pair a ready.timeoutMs with either log or port in config templates","Treat empty-string log patterns as absent — trim and omit them","Add a config linter rule that flags ready sections without a target","Prefer port probes when the service exposes one; they are the least ambiguous"],"tags":["validation","configuration","ipc","ready-check"],"backgroundTag":"schema-validation-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}