unclecode/crawl4ai · warning · TimeoutError
Timeout after {timeout}ms waiting for selector '{wait_for}'
Error message
Timeout after {timeout}ms waiting for selector '{wait_for}' What it means
ArtifactNotFound (no message) is raised by resolve_artifact when the supplied artifact_id is not a string matching the strict 32-lowercase-hex pattern. Because the same exception covers invalid, missing, special-file, and expired cases, the server never reveals whether an artifact exists — an anti-enumeration design.
Source
Thrown at crawl4ai/async_crawler_strategy.py:281
except Error as e:
if "Timeout" in str(e):
raise TimeoutError(
f"Timeout after {timeout}ms waiting for selector '{css_selector}'"
)
else:
raise ValueError(f"Invalid CSS selector: '{css_selector}'")
else:
# Auto-detect based on content
if wait_for.startswith("()") or wait_for.startswith("function"):
# It's likely a JavaScript function
return await self.csp_compliant_wait(page, wait_for, timeout)
else:
# Assume it's a CSS selector first
try:
await page.wait_for_selector(wait_for, timeout=timeout)
except Error as e:
if "Timeout" in str(e):
raise TimeoutError(
f"Timeout after {timeout}ms waiting for selector '{wait_for}'"
)
else:
# If it's not a timeout error, it might be an invalid selector
# Let's try to evaluate it as a JavaScript function as a fallback
try:
return await self.csp_compliant_wait(
page, f"() => {{{wait_for}}}", timeout
)
except Error:
raise ValueError(
f"Invalid wait_for parameter: '{wait_for}'. "
"It should be either a valid CSS selector, a JavaScript function, "
"or explicitly prefixed with 'js:' or 'css:'."
)
async def csp_compliant_wait(
self, page: Page, user_wait_function: str, timeout: float = 30000View on GitHub (pinned to 7e80152142)
Solutions
- Use the exact artifact_id returned by write_artifact (32 hex chars) without modification
- Treat any 404 from this endpoint uniformly: the id is wrong, expired, or never existed — re-generate the artifact rather than probing
- If ids round-trip through your own storage, keep them as opaque strings and never reformat them
Example fix
# before GET /artifact/3F2A...-shortened # after GET /artifact/3f2a9c... # exact 32-char lowercase hex from the write_artifact response
Defensive patterns
Strategy: type-guard
Type guard
import re
_HEX32 = re.compile(r"^[0-9a-f]{32}$")
def is_valid_artifact_id(artifact_id) -> bool:
return isinstance(artifact_id, str) and bool(_HEX32.match(artifact_id)) Try / catch
from artifacts import resolve_artifact, ArtifactNotFound
try:
path, mime = resolve_artifact(artifact_id)
except ArtifactNotFound:
return Response(status_code=404) # never probe or distinguish causes Prevention
- Store artifact ids as opaque 32-char lowercase hex strings; never reformat them
- Validate shape client-side before the request to fail fast
- Treat every 404 as 'gone/invalid'; regenerate instead of enumerating
When it happens
Trigger: GETing /artifact/{id} with a malformed id: wrong length, uppercase hex, non-hex characters, or a non-string value. The regex check `_HEX32.match(artifact_id)` fails before any filesystem access.
Common situations: Truncating or typo-ing the 32-char id when copying it; URL-encoding damage (e.g. '+' injected); passing an internal filename or a UUID with dashes instead of the server-issued hex id.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Invalid wait_for parameter: '{wait_for}'. It should be eithe
- Invalid config
- Request failed
- Container not found: ${config.container_selector}
- Timeout after {timeout}ms waiting for selector '{css_selecto
AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14).
Data as JSON: /api/errors/64996e97c645c21a.
Report an issue: GitHub.