{"record":{"id":"64996e97c645c21a","repo":"unclecode/crawl4ai","slug":"timeout-after-timeout-ms-waiting-for-selector-w","errorCode":null,"errorMessage":"Timeout after {timeout}ms waiting for selector '{wait_for}'","messagePattern":"Timeout after (.+?)ms waiting for selector '(.+?)'","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"warning","filePath":"crawl4ai/async_crawler_strategy.py","lineNumber":281,"sourceCode":"            except Error as e:\n                if \"Timeout\" in str(e):\n                    raise TimeoutError(\n                        f\"Timeout after {timeout}ms waiting for selector '{css_selector}'\"\n                    )\n                else:\n                    raise ValueError(f\"Invalid CSS selector: '{css_selector}'\")\n        else:\n            # Auto-detect based on content\n            if wait_for.startswith(\"()\") or wait_for.startswith(\"function\"):\n                # It's likely a JavaScript function\n                return await self.csp_compliant_wait(page, wait_for, timeout)\n            else:\n                # Assume it's a CSS selector first\n                try:\n                    await page.wait_for_selector(wait_for, timeout=timeout)\n                except Error as e:\n                    if \"Timeout\" in str(e):\n                        raise TimeoutError(\n                            f\"Timeout after {timeout}ms waiting for selector '{wait_for}'\"\n                        )\n                    else:\n                        # If it's not a timeout error, it might be an invalid selector\n                        # Let's try to evaluate it as a JavaScript function as a fallback\n                        try:\n                            return await self.csp_compliant_wait(\n                                page, f\"() => {{{wait_for}}}\", timeout\n                            )\n                        except Error:\n                            raise ValueError(\n                                f\"Invalid wait_for parameter: '{wait_for}'. \"\n                                \"It should be either a valid CSS selector, a JavaScript function, \"\n                                \"or explicitly prefixed with 'js:' or 'css:'.\"\n                            )\n\n    async def csp_compliant_wait(\n        self, page: Page, user_wait_function: str, timeout: float = 30000","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/crawl4ai/async_crawler_strategy.py#L263-L299","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nGET /artifact/3F2A...-shortened\n\n# after\nGET /artifact/3f2a9c...  # exact 32-char lowercase hex from the write_artifact response","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"import re\n_HEX32 = re.compile(r\"^[0-9a-f]{32}$\")\ndef is_valid_artifact_id(artifact_id) -> bool:\n    return isinstance(artifact_id, str) and bool(_HEX32.match(artifact_id))","tryCatchPattern":"from artifacts import resolve_artifact, ArtifactNotFound\ntry:\n    path, mime = resolve_artifact(artifact_id)\nexcept ArtifactNotFound:\n    return Response(status_code=404)  # never probe or distinguish causes","preventionTips":["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"],"tags":["artifacts","not-found","validation","opaque-id"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}