{"record":{"id":"3b6a24185abaf0c8","repo":"can1357/oh-my-pi","slug":"label-failed-fast-after-afterms-ms-formatsel","errorCode":null,"errorMessage":"${label} failed fast after ${afterMs}ms${formatSelectorMatchHint(0)}","messagePattern":"(.+?) failed fast after (.+?)ms(.+?)","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/tab-worker.ts","lineNumber":1523,"sourceCode":"\t * matches; an inconclusive probe (mid-navigation, detached frame) never counts\n\t * toward the zero-match window.\n\t */\n\tasync #zeroMatchWatchdog(selector: string, label: string, afterMs: number, signal: AbortSignal): Promise<never> {\n\t\tconst page = this.#requirePage();\n\t\tconst resolved = normalizeSelector(selector);\n\t\tconst deadline = Date.now() + afterMs;\n\t\twhile (!signal.aborted) {\n\t\t\tlet count: number | null = null;\n\t\t\ttry {\n\t\t\t\tconst handles = await page.$$(resolved);\n\t\t\t\tcount = handles.length;\n\t\t\t\tfor (const handle of handles) void handle.dispose().catch(() => undefined);\n\t\t\t} catch {\n\t\t\t\t// Inconclusive probe — keep polling without advancing toward failure.\n\t\t\t}\n\t\t\tif (count !== null && count > 0) break;\n\t\t\tif (count === 0 && Date.now() >= deadline) {\n\t\t\t\tthrow new ToolError(`${label} failed fast after ${afterMs}ms${formatSelectorMatchHint(0)}`);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait untilAborted(signal, () => Bun.sleep(ZERO_MATCH_POLL_MS));\n\t\t\t} catch {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn await new Promise<never>(() => {});\n\t}\n\n\t/**\n\t * Best-effort match-count probe for a timed-out selector op. Never throws;\n\t * empty string when the probe fails, stalls, or the selector is an aria-ref.\n\t */\n\tasync #selectorTimeoutHint(selector: string): Promise<string> {\n\t\tif (parseAriaRefSelector(selector) !== null) return \"\";\n\t\ttry {\n\t\t\tconst handles = await Promise.race([","sourceCodeStart":1505,"sourceCodeEnd":1541,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/tab-worker.ts#L1505-L1541","documentation":"A wait-for-disappearance style poll confirmed the selector matches exactly 0 elements and, after reaching the deadline with zero matches still true, fails fast with this ToolError including elapsed ms and a selector match hint. It distinguishes 'element never appeared' from 'element did not disappear in time'.","triggerScenarios":"Waiting for an element/selector to disappear (count must reach 0) where the first probe already returned count === 0 and the fast-fail deadline (afterMs) elapsed, so the condition was satisfied-but-never-pending — typically a wrong selector that never matched anything.","commonSituations":"Selector typo or wrong frame/shadow-root scope so it never matches; asserting a spinner disappears when the page never rendered it; CSS/XPath mismatch after a DOM redesign.","solutions":["Verify the selector actually matches elements in the intended frame (query it directly with page.$)","Check whether the element ever existed — if it should appear first, wait for appearance before waiting for disappearance","Use ariaSnapshot to inspect the current DOM and correct the selector","Increase the timeout only after confirming the element is present and the disappearance is genuinely pending"],"exampleFix":"// before\nawait tab.waitForSelectorGone('.spinner', { timeout: 3000 });\n// after\nif (await tab.$('.spinner')) {\n  await tab.waitForSelectorGone('.spinner', { timeout: 10000 });\n}","handlingStrategy":"validation","validationCode":"const present = await tab.$(selector);\nif (!present) throw new Error(`selector ${selector} never matched — fix it before waiting for disappearance`);","typeGuard":null,"tryCatchPattern":"try {\n  await waitForGone(selector, { timeout: ms });\n} catch (err) {\n  if (err instanceof ToolError && /failed fast/.test(err.message)) {\n    // selector matched nothing from the start: treat as selector bug\n    const snap = await tab.ariaSnapshot();\n    logger.warn('selector never matched', { selector, snap });\n  } else throw err;\n}","preventionTips":["Confirm the element exists before waiting for it to disappear","Test selectors with page.$ or a full ariaSnapshot first","Scope selectors to the correct frame/shadow root","Distinguish 'never matched' from 'timed out waiting' when choosing timeouts"],"tags":["selector","wait","timeout","browser-automation"],"backgroundTag":"selector-no-match","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}