CloakHQ/CloakBrowser · error · ActionabilityError
Element ${selector} failed timeout check: timeout expired be
Error message
Element ${selector} failed timeout check: timeout expired before first check What it means
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.
Source
Thrown at js/src/human/actionability.ts:155
export async function ensureActionable(
pageOrFrame: Page | Frame,
selector: string,
checks: ReadonlySet<CheckName>,
timeout: number = 30000,
force: boolean = false,
): Promise<void> {
if (force) return;
const deadline = Date.now() + timeout;
let attempt = 0;
let lastError: Error | null = null;
while (true) {
const remainingMs = Math.max(0, deadline - Date.now());
if (remainingMs <= 0) {
if (lastError) throw lastError;
throw new ActionabilityError(selector, 'timeout', 'timeout expired before first check');
}
try {
await stealthActionable(pageOrFrame, selector, checks);
return;
} catch (error) {
if (error instanceof ActionabilityError || error instanceof StealthEvaluationError) {
lastError = error;
if (Date.now() >= deadline) throw lastError;
await backoffSleep(attempt++);
} else {
throw error;
}
}
}
}
// ---------------------------------------------------------------------------View on GitHub (pinned to d6bad5de26)
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)
Example fix
// before
await humanClick(page, '#btn', { timeout: 0 });
// after
await humanClick(page, '#btn', { timeout: 5000 }); Defensive patterns
Strategy: validation
Validate before calling
const timeoutMs = 5000;
if (!(timeoutMs > 0)) throw new Error('timeout must be positive'); Type guard
function isActionabilityTimeoutError(e: unknown): e is ActionabilityError {
return e instanceof Error && /failed timeout check/.test(e.message);
} Try / catch
try { await humanClick(page, sel, { timeout: 5000 }); } catch (e) { if (isActionabilityTimeoutError(e)) { /* fix preconditions, then retry */ } else throw e; } Prevention
- 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)
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- timeout
- Isolated-world DOM evaluation failed for ${selector}
- Element ${selector} failed attached check: element not found
- Element ${selector} failed visible check: element is not vis
- Element ${selector} failed enabled check: element is disable
AI-assisted analysis of CloakHQ/CloakBrowser@d6bad5de26 (2026-08-28).
Data as JSON: /api/errors/f7df556dfc1f508b.
Report an issue: GitHub.