{"record":{"id":"7bda9c300b690a9c","repo":"unclecode/crawl4ai","slug":"hooks-are-disabled-set-crawl4ai-hooks-enabled-tru","errorCode":null,"errorMessage":"Hooks are disabled. Set CRAWL4AI_HOOKS_ENABLED=true to enable.","messagePattern":"Hooks are disabled\\. Set CRAWL4AI_HOOKS_ENABLED=true to enable\\.","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"warning","filePath":"deploy/docker/server.py","lineNumber":882,"sourceCode":"\n\n@app.post(\"/crawl\")\n@limiter.limit(config[\"rate_limiting\"][\"default_limit\"])\n@mcp_tool(\"crawl\")\nasync def crawl(\n    request: Request,\n    crawl_request: CrawlRequestWithHooks,\n    _td: Dict = Depends(token_dep),\n):\n    \"\"\"\n    Crawl a list of URLs and return the results as JSON.\n    For streaming responses, use /crawl/stream endpoint.\n    Supports optional user-provided hook functions for customization.\n    \"\"\"\n    if not crawl_request.urls:\n        raise HTTPException(400, \"At least one URL required\")\n    if crawl_request.hooks and not HOOKS_ENABLED:\n        raise HTTPException(403, \"Hooks are disabled. Set CRAWL4AI_HOOKS_ENABLED=true to enable.\")\n    # Check whether it is a redirection for a streaming request\n    try:\n        crawler_config = CrawlerRunConfig.load(\n            crawl_request.crawler_config, provenance=Provenance.UNTRUSTED\n        )\n    except UntrustedConfigError as e:\n        raise HTTPException(400, f\"Rejected config: {e}\")\n    if crawler_config.stream:\n        return await stream_process(crawl_request=crawl_request)\n    \n    # Prepare hooks config if provided\n    hooks_config = None\n    if crawl_request.hooks:\n        hooks_config = {\n            'hooks': crawl_request.hooks.hooks,\n            'timeout': crawl_request.hooks.timeout\n        }\n    ","sourceCodeStart":864,"sourceCodeEnd":900,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/deploy/docker/server.py#L864-L900","documentation":"A 403 from POST /crawl: the request included hooks (user-supplied hook functions) but the server has CRAWL4AI_HOOKS_ENABLED unset/false. Hook execution is a dangerous feature (running user code server-side) and is gated behind an environment flag, off by default. The check fires only when crawl_request.hooks is truthy.","triggerScenarios":"POST /crawl with a non-null hooks field in CrawlRequestWithHooks (hooks + timeout) on any deployment that doesn't set CRAWL4AI_HOOKS_ENABLED=true. Requests without hooks succeed regardless of the flag.","commonSituations":"Client code upgraded to send hooks while the server kept default env; copying example payloads that include hooks; multi-tenant deployments where only some instances enable hooks.","solutions":["Set CRAWL4AI_HOOKS_ENABLED=true in the server environment and restart, if trusted-hook execution is intended.","If hooks aren't needed, omit the hooks field from the crawl request entirely.","Feature-detect server capabilities before sending hooks (e.g. config/schema endpoint) or catch 403 and retry hook-less."],"exampleFix":"# before\nbody = {'urls': [u], 'hooks': {'hooks': [...], 'timeout': 5}}\n# after (server default)\nbody = {'urls': [u]}\n# or enable server-side: CRAWL4AI_HOOKS_ENABLED=true","handlingStrategy":"validation","validationCode":"def crawl_body_no_hooks(urls: list[str]) -> dict:\n    return {'urls': urls}  # omit hooks entirely unless you know HOOKS_ENABLED=true","typeGuard":null,"tryCatchPattern":"resp = requests.post(f'{BASE}/crawl', json=body, headers=hdrs)\nif resp.status_code == 403 and 'CRAWL4AI_HOOKS_ENABLED' in resp.text:\n    body.pop('hooks', None)\n    resp = requests.post(f'{BASE}/crawl', json=body, headers=hdrs)  # graceful hook-less retry","preventionTips":["Only include the hooks field when the deployment explicitly enables it.","Catch the 403 and degrade gracefully to a hook-less crawl.","Document per-deployment feature flags (execute_js, hooks) in client configuration."],"tags":["hooks","feature-flag","http-403","security","environment"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}