{"record":{"id":"da0cc50cb9583b8a","repo":"unclecode/crawl4ai","slug":"error-evaluating-condition-error-message","errorCode":null,"errorMessage":"Error evaluating condition: ${{error.message}}","messagePattern":"Error evaluating condition: (.+?)\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crawl4ai/async_crawler_strategy.py","lineNumber":330,"sourceCode":"        Raises:\n            RuntimeError: If there's an error evaluating the condition\n        \"\"\"\n        wrapper_js = f\"\"\"\n        async () => {{\n            const userFunction = {user_wait_function};\n            const startTime = Date.now();\n            try {{\n                while (true) {{\n                    if (await userFunction()) {{\n                        return true;\n                    }}\n                    if (Date.now() - startTime > {timeout}) {{\n                        return false;  // Return false instead of throwing\n                    }}\n                    await new Promise(resolve => setTimeout(resolve, 100));\n                }}\n            }} catch (error) {{\n                throw new Error(`Error evaluating condition: ${{error.message}}`);\n            }}\n        }}\n        \"\"\"\n\n        try:\n            result = await self.adapter.evaluate(page, wrapper_js)\n            return result\n        except Exception as e:\n            if \"Error evaluating condition\" in str(e):\n                raise RuntimeError(f\"Failed to evaluate wait condition: {str(e)}\")\n            # For timeout or other cases, just return False\n            return False\n\n    async def process_iframes(self, page):\n        \"\"\"\n        Process iframes on a page. This function will extract the content of each iframe and replace it with a div containing the extracted content.\n\n        Args:","sourceCodeStart":312,"sourceCodeEnd":348,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/crawl4ai/async_crawler_strategy.py#L312-L348","documentation":"Raised as an HTTP 500 from the crawl endpoint's catch-all handler when any unexpected exception escapes a (non-streaming) crawl request. The response body is a JSON-encoded string carrying the original error message plus the server's memory delta and peak RSS for that request. It is a last-resort signal: the real cause is whatever `e` is, so check the server logs for the logged exception and the `error` field for the underlying message.","triggerScenarios":"POSTing a crawl request to /crawl (or a similar non-stream endpoint) whose processing throws an unhandled exception after the browser pool is engaged — e.g. target page render crashes, Playwright browser dies mid-run, or an unexpected library error inside crawler execution. The endpoint wraps it as HTTPException(500, json.dumps({error, server_memory_delta_mb, server_peak_memory_mb})).","commonSituations":"Crawling pages that exhaust browser memory (large delta/peak values in the detail are the tell), incompatible Playwright/Chromium versions after an image upgrade, a target site returning payloads that break the scraping strategy, or transient network failures inside the crawl itself.","solutions":["Read the `error` field in the 500 detail — it is the message of the underlying exception; treat this 500 as a wrapper, not the root cause","Check server logs for the full traceback (the handler logs/monitors the exception before re-raising) and fix the underlying fault","If server_memory_delta_mb is large, reduce parallelism / page count per browser or cap page resources in crawler_config to avoid OOM-driven crashes","Retry with exponential backoff for transient site/network failures; if it reproduces on one URL, crawl that URL in isolation to get a cleaner stack trace"],"exampleFix":"# before\nresp = await client.post(\"/crawl\", json=payload)\nresp.raise_for_status()\n\n# after\nresp = await client.post(\"/crawl\", json=payload)\nif resp.status_code == 500:\n    detail = json.loads(resp.json()[\"detail\"])\n    logging.error(\"crawl failed: %s (server mem delta %.1f MB)\",\n                  detail[\"error\"], detail.get(\"server_memory_delta_mb\", 0))\nresp.raise_for_status()","handlingStrategy":"retry","validationCode":"# validate payload shape client-side to avoid avoidable 500s\nimport json\ndef valid_crawl_payload(p):\n    assert isinstance(p.get(\"urls\"), list) and p[\"urls\"], \"urls required\"\n    assert all(u.lower().startswith((\"http://\", \"https://\")) for u in p[\"urls\"])\n    return True","typeGuard":null,"tryCatchPattern":"try:\n    resp = await client.post(\"/crawl\", json=payload)\n    resp.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 500:\n        detail = json.loads(e.response.json()[\"detail\"])\n        log.error(\"crawl failed: %s\", detail[\"error\"])  # real cause\n    raise","preventionTips":["Treat the 500 detail's `error` field as the root cause; this handler is only a wrapper","Cap concurrency and per-page resources so server_memory_delta_mb stays small","Retry transient 500s with exponential backoff and jitter, bounded attempts"],"tags":["http-500","crawl","server-error","memory"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}