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 = 30000

View on GitHub (pinned to 7e80152142)

Solutions

  1. Use the exact artifact_id returned by write_artifact (32 hex chars) without modification
  2. Treat any 404 from this endpoint uniformly: the id is wrong, expired, or never existed — re-generate the artifact rather than probing
  3. 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

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

Related errors


AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14). Data as JSON: /api/errors/64996e97c645c21a. Report an issue: GitHub.