{"record":{"id":"06b1f54eff15c81e","repo":"can1357/oh-my-pi","slug":"wait-expects-milliseconds-number-or-a-predi","errorCode":null,"errorMessage":"wait(...) expects milliseconds (number) or a predicate function to poll","messagePattern":"wait\\(\\.\\.\\.\\) expects milliseconds \\(number\\) or a predicate function to poll","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/run-scope.ts","lineNumber":349,"sourceCode":" * - `wait(ms)` sleeps for `ms` milliseconds.\n * - `wait(fn, { timeout?, interval? })` polls `fn` (sync or async) until it returns a\n *   truthy value and resolves with that value; throws a named `ToolError` on timeout\n *   instead of stalling into the whole-cell deadline. Predicate errors propagate.\n */\nexport function waitForRun(\n\tmsOrPredicate: number | (() => unknown),\n\tsignal: AbortSignal,\n\topts?: WaitPredicateOptions,\n): Promise<unknown> {\n\tconst promise = (async (): Promise<unknown> => {\n\t\tthrowIfAborted(signal);\n\t\tif (typeof msOrPredicate === \"number\") {\n\t\t\tawait untilAborted(signal, async () => await Bun.sleep(msOrPredicate));\n\t\t\tthrowIfAborted(signal);\n\t\t\treturn undefined;\n\t\t}\n\t\tif (typeof msOrPredicate !== \"function\") {\n\t\t\tthrow new ToolError(\"wait(...) expects milliseconds (number) or a predicate function to poll\");\n\t\t}\n\t\tconst timeout =\n\t\t\topts?.timeout !== undefined && Number.isFinite(opts.timeout) && opts.timeout > 0\n\t\t\t\t? opts.timeout\n\t\t\t\t: DEFAULT_PREDICATE_TIMEOUT_MS;\n\t\tconst interval = Math.max(opts?.interval ?? 100, 10);\n\t\tconst deadline = Date.now() + timeout;\n\t\tfor (;;) {\n\t\t\tconst value = await untilAborted(signal, async () => await msOrPredicate());\n\t\t\tthrowIfAborted(signal);\n\t\t\tif (value) return value;\n\t\t\tif (Date.now() + interval > deadline) {\n\t\t\t\tthrow new ToolError(`wait(predicate) timed out after ${timeout}ms — predicate never returned truthy`);\n\t\t\t}\n\t\t\tawait untilAborted(signal, async () => await Bun.sleep(interval));\n\t\t}\n\t})();\n\treturn trackBrowserRunPromise(promise);","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/run-scope.ts#L331-L367","documentation":"waitForRun in run-scope accepts either a number of milliseconds to sleep or a predicate function to poll. If msOrPredicate is neither (e.g. a string, undefined, or an object), it throws this ToolError immediately instead of guessing a default.","triggerScenarios":"wait(\"1000\") (string instead of number), wait() with no argument, wait({ms: 100}), or passing a predicate-looking object that is not callable — typically from JSON-serialized tool arguments where functions become strings/objects.","commonSituations":"LLM passes duration as a quoted string in JSON args; code forwards an optional value that was undefined; a wrapper binds the wrong argument position.","solutions":["Pass a number: wait(1000) or wait(predicateFn)","Coerce string durations with Number() before calling","If polling, pass an actual function, not a boolean/expression result"],"exampleFix":"// before\nawait wait(\"500\");\n// after\nawait wait(500); // or\nawait wait(() => run.status === \"done\");","handlingStrategy":"type-guard","validationCode":"if (typeof arg !== \"number\" && typeof arg !== \"function\") throw new Error(\"wait() needs a number (ms) or a predicate function\");","typeGuard":"function isWaitArg(v: unknown): v is number | ((run: RunState) => unknown) { return typeof v === \"number\" || typeof v === \"function\"; }","tryCatchPattern":"try { await wait(arg); } catch (e) { if (e instanceof ToolError && e.message.includes(\"expects milliseconds\")) { await wait(Number(arg) || DEFAULT_MS); } else throw e; }","preventionTips":["Never quote numeric durations in serialized tool arguments","Coerce string durations with Number() at the boundary","Pass predicate polling as an inline arrow function, not its result"],"tags":["type-error","arguments","validation"],"backgroundTag":"invalid-argument-type","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}