{"record":{"id":"9a23201d9f04dd9c","repo":"can1357/oh-my-pi","slug":"ready-requires-log-or-port-9a2320","errorCode":null,"errorMessage":"ready requires log or port","messagePattern":"ready requires log or port","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/hub/launch.ts","lineNumber":190,"sourceCode":"function requiredName(params: LaunchParams): string {\n\tif (!params.name) throw new ToolError(`${params.op} requires name`);\n\treturn params.name;\n}\n\nfunction timeoutMs(value: number | undefined, fallbackSeconds: number): number {\n\tconst seconds = Math.max(0.05, Math.min(3_600, value ?? fallbackSeconds));\n\treturn Math.round(seconds * 1_000);\n}\n\nfunction commandSpec(params: LaunchParams, session: ToolSession): DaemonSpec {\n\tconst name = requiredName(params);\n\tif (!params.application) throw new ToolError(\"start requires application\");\n\tconst ready = params.ready;\n\tconst detached = params.detached ?? false;\n\tif (ready?.port !== undefined && (!Number.isInteger(ready.port) || ready.port < 1 || ready.port > 65_535)) {\n\t\tthrow new ToolError(\"ready.port must be an integer from 1 to 65535\");\n\t}\n\tif (ready && !ready.log && ready.port === undefined) throw new ToolError(\"ready requires log or port\");\n\treturn {\n\t\tname,\n\t\tapplication: params.application,\n\t\targs: params.args ?? [],\n\t\tenv: params.env ?? {},\n\t\tcwd: resolveToCwd(params.cwd ?? session.cwd, session.cwd),\n\t\tpty: detached ? false : (params.pty ?? true),\n\t\tready: ready\n\t\t\t? {\n\t\t\t\t\tlog: ready.log,\n\t\t\t\t\tport: ready.port,\n\t\t\t\t\thost: ready.host,\n\t\t\t\t\ttimeoutMs: timeoutMs(ready.timeout, 30),\n\t\t\t\t}\n\t\t\t: undefined,\n\t\trestart: params.restart ?? \"no\",\n\t\tpersist: (params.persist ?? false) || detached,\n\t\tdetached,","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/hub/launch.ts#L172-L208","documentation":"A `ready` condition must give the tool some way to detect readiness — either a log substring (`ready.log`) or a port (`ready.port`). commandSpec() throws this ToolError when `ready` is provided but both fields are absent/empty, making the readiness condition undetectable.","triggerScenarios":"launch({ op: \"start\", name, application, ready: {} }) or ready: { } after JSON filtering removed empty log/port values.","commonSituations":"Model emitted an empty ready object as a placeholder; caller intended to wait unconditionally and added ready with no fields; serialization dropped falsy values leaving an empty ready.","solutions":["Add a detection source: ready: { log: \"listening on\" } or ready: { port: 3000 }.","If no readiness signal is needed, omit the `ready` parameter entirely instead of passing an empty object.","Combine both (log + port) if you want a stronger readiness check."],"exampleFix":"// before\nlaunch({ op: \"start\", name: \"api\", application: \"bun\", ready: {} })\n// throws: ready requires log or port\n\n// after\nlaunch({ op: \"start\", name: \"api\", application: \"bun\", ready: { log: \"Listening\" } })","handlingStrategy":"validation","validationCode":"if (params.ready && !params.ready.log && params.ready.port === undefined) {\n  throw new Error(\"ready requires log or port\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  await launchTool.run({ op: \"start\", name, application, ready });\n} catch (err) {\n  if (err instanceof ToolError && err.message === \"ready requires log or port\") {\n    // add ready.log or ready.port, or drop ready entirely\n  } else throw err;\n}","preventionTips":["Never emit an empty ready object; omit it if no readiness signal is needed.","Always pair a readiness wait with a log substring or port.","Watch JSON serialization that strips falsy fields from ready."],"tags":["validation","readiness","tool-args"],"backgroundTag":"missing-required-parameter","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}