{"record":{"id":"f7df556dfc1f508b","repo":"CloakHQ/CloakBrowser","slug":"element-selector-failed-timeout-check-timeout","errorCode":null,"errorMessage":"Element ${selector} failed timeout check: timeout expired before first check","messagePattern":"Element (.+?) failed timeout check: timeout expired before first check","errorType":"exception","errorClass":"ActionabilityError","httpStatus":null,"severity":"error","filePath":"js/src/human/actionability.ts","lineNumber":155,"sourceCode":"\nexport async function ensureActionable(\n  pageOrFrame: Page | Frame,\n  selector: string,\n  checks: ReadonlySet<CheckName>,\n  timeout: number = 30000,\n  force: boolean = false,\n): Promise<void> {\n  if (force) return;\n\n  const deadline = Date.now() + timeout;\n  let attempt = 0;\n  let lastError: Error | null = null;\n\n  while (true) {\n    const remainingMs = Math.max(0, deadline - Date.now());\n    if (remainingMs <= 0) {\n      if (lastError) throw lastError;\n      throw new ActionabilityError(selector, 'timeout', 'timeout expired before first check');\n    }\n\n    try {\n      await stealthActionable(pageOrFrame, selector, checks);\n      return;\n    } catch (error) {\n      if (error instanceof ActionabilityError || error instanceof StealthEvaluationError) {\n        lastError = error;\n        if (Date.now() >= deadline) throw lastError;\n        await backoffSleep(attempt++);\n      } else {\n        throw error;\n      }\n    }\n  }\n}\n\n// ---------------------------------------------------------------------------","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/CloakHQ/CloakBrowser/blob/d6bad5de261bedf025280ace1d14e800aee13923/js/src/human/actionability.ts#L137-L173","documentation":"ensureActionable polls stealthActionable until a deadline; if the deadline expires before any check has succeeded (and no more specific error was captured), this generic timeout error is thrown. If a prior iteration recorded a lastError, that error is rethrown instead — so seeing this message means the loop never completed a check with no captured error.","triggerScenarios":"Passing timeout: 0 (or a deadline already in the past) so remainingMs <= 0 on the first iteration with lastError === null, or a clock/timing edge where the first computed remaining time is non-positive.","commonSituations":"Explicitly setting timeout to 0 expecting 'no wait' semantics, system clock skew or suspended VMs making Date.now jump forward, or an action scheduled after its own deadline in CI.","solutions":["Pass a positive timeout matching how long the element realistically takes to become actionable","If you intended an instant check, still pass a small nonzero timeout (e.g. 50-100ms) so at least one check runs","Synchronize on element state before calling the humanized action instead of relying on the internal poll loop","Guard against clock skew in CI containers (sync time, avoid suspended VMs)"],"exampleFix":"// before\nawait humanClick(page, '#btn', { timeout: 0 });\n// after\nawait humanClick(page, '#btn', { timeout: 5000 });","handlingStrategy":"validation","validationCode":"const timeoutMs = 5000;\nif (!(timeoutMs > 0)) throw new Error('timeout must be positive');","typeGuard":"function isActionabilityTimeoutError(e: unknown): e is ActionabilityError {\n  return e instanceof Error && /failed timeout check/.test(e.message);\n}","tryCatchPattern":"try { await humanClick(page, sel, { timeout: 5000 }); } catch (e) { if (isActionabilityTimeoutError(e)) { /* fix preconditions, then retry */ } else throw e; }","preventionTips":["Always pass a positive timeout to humanized actions","Sync on element state before acting instead of relying on the internal poll","Use generous timeouts in slow environments (CI, throttled CPU)"],"tags":["timeout","actionability","polling","deadline"],"backgroundTag":"action-timeout-expired","analyzedSha":"d6bad5de261bedf025280ace1d14e800aee13923","analyzedAt":"2026-08-28T14:13:12.918Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}