Repository navigation
Open
Labels
🐞 BugSomething isn't workingSomething isn't working📌 Root causedidentified the root cause of bugidentified the root cause of bug
Description
crawl4ai version
0.9.5 (Docker image unclecode/crawl4ai:0.9.5); the same BrowserConfig code is on develop.
Expected Behavior
The default BrowserConfig.user_agent matches the browser crawl4ai launches.
An empty user_agent (to keep the browser's own User-Agent) sends no forged client hints.
Current Behavior
- Stale, non-standard default.
BrowserConfig.user_agentdefaults to
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/116.0.0.0 Safari/537.36
(crawl4ai/async_configs.py, also thefallbackofuser_agent_generator.py).
It lacks the(KHTML, like Gecko)token Chrome sends, and announces Chrome 116, while the 0.9.5 image runs Playwright 1.64 with Chromium 156.0.8078.4.
The generatedsec-ch-uasays 116 too, whereas the other request headers (Accept-Encoding: … zstd,Priority: u=0, i) come from Chromium 156.
TheBrowserConfigdocstring documents yet another default, a well-formed macOS User-Agent.
Since Playwright is not pinned (playwright>=1.49.0), the bundled Chromium changes with each image build, so a hard-coded string keeps drifting. - Empty
sec-ch-uawith an empty User-Agent.BrowserConfig(user_agent="")keeps the browser's native User-Agent, but__init__still runsself.headers.setdefault("sec-ch-ua", self.browser_hint), andgenerate_client_hints("")returns'': every request then carries an emptysec-ch-uaheader. - A worker keeps the real User-Agent. With the default config, CreepJS reports in its
ServiceWorkerGlobalScopeworker the User-Agent… HeadlessChrome/156.0.0.0 …, while the page saysChrome/116.
Passing the same string as a launch argument (--user-agent=…inextra_args) makes the worker report it too.
(Observed on the 0.9.5 image with fix(browser): pass the default chromium channel again, fall back to the shell #2448 backported; the User-Agent code is the same without it.)
Related: #2472 / #2473 touch the same auto-added sec-ch-ua, but #2473 only leaves it out of dump(); __init__ is unchanged.
Is this reproducible?
Yes
Inputs Causing the Bug
- URL(s): https://httpbin.org/headers (headers sent), https://abrahamjuliot.github.io/creepjs/ (worker User-Agent)
- Settings: default BrowserConfig; Docker server /md endpointSteps to Reproduce
docker run -d --name c4ai --shm-size=1g -p 11235:11235 -e CRAWL4AI_API_TOKEN=test unclecode/crawl4ai:0.9.5
curl -s -X POST http://localhost:11235/md -H "Authorization: Bearer test" \
-H "Content-Type: application/json" -d '{"url": "https://httpbin.org/headers", "f": "raw"}'
# "User-Agent" and "Sec-Ch-Ua" announce Chrome 116; the bundled Chromium is 156:
docker exec c4ai sh -c 'grep -A3 "\"chromium\"" /usr/local/lib/python3.12/site-packages/playwright/driver/package/browsers.json'Code snippets
from crawl4ai import BrowserConfig
print(BrowserConfig().user_agent)
# Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/116.0.0.0 Safari/537.36
print(BrowserConfig(user_agent="").headers)
# {'sec-ch-ua': ''}Suggested fix
- Default to the launched browser's own User-Agent (with
HeadlessChromereplaced byChromein headless mode) rather than a hard-coded string; at least add(KHTML, like Gecko)and keep the version in step with the bundled Chromium. - Skip the
sec-ch-uadefault whenuser_agentis empty. - Also pass the User-Agent as
--user-agentat launch, so workers report the same one.
OS
Linux (Docker on WSL2)
Python version
3.12 (image)
Browser
Chromium bundled with Playwright 1.64 (the stock 0.9.5 image launches chrome-headless-shell, see #2382)
Browser version
156.0.8078.4
Metadata
Metadata
Assignees
Labels
🐞 BugSomething isn't workingSomething isn't working📌 Root causedidentified the root cause of bugidentified the root cause of bug
Activity
ntohidi commentedon Nov 10, 2026
Thanks for the detailed report. I reproduced all three on develop with Playwright 1.53 / Chromium 138, so it isn't specific to the Docker image.
Root causes:
BrowserConfig.__init__and as thefallbackinuser_agent_generator.py, while the bundled Chromium moves with each Playwright release. The docstring default is out of sync too.sec-ch-ua.generate_client_hints()returns""when the UA has no browser token, and that value gets sent without a check in three places:BrowserConfig.__init__(headers.setdefault),BrowserManagerwhere it buildscombined_headers, and the per-run UA sync inAsyncPlaywrightCrawlerStrategy. Fix: Do not send an empty sec-ch-ua header for empty or unparseable user agents #2495 guards only the first one, souser_agent="MyBot/1.0"still sends an empty header with that PR applied.--user-agentat launch fixes the service worker in my test, but it applies to the whole browser, so a per-run orrandomUA would still mismatch there.We'll fix the empty header first, at all three sites. The default UA needs a decision on whether to read it from the launched browser or just correct the static string.