Why Hardcoding a Selenium User-Agent Gets You Caught — Chrome Version Mismatch and Bot Detection
Setting a custom User-Agent string in Selenium is one of the most common "stealth" techniques. You copy a real Chrome UA string into your code, and it works — sometimes for months. Then one day, CAPTCHAs start appearing on every other request. The culprit? Chrome auto-updated, but your hardcoded UA stayed frozen in time. Here is the exact mechanism behind version mismatch bot detection, based on a real incident whereChrome/114 in the UA collided with a Chrome 146 browser.
The Setup: A Year of Silence, Then CAPTCHAs
In early 2025, we built a Selenium automation script and set the User-Agent to mimic a real Chrome browser:
options.add_argument(
"user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/114.0.0.0 Safari/537.36"
)Chrome 114 was the latest version at the time. The script ran flawlessly for over a year — no CAPTCHAs, no blocks, no issues. Then in March 2026, login automation started hitting CAPTCHAs roughly 50% of the time.
After hours of debugging, we checked the actual Chrome version on the machine: Chrome 146. The code was sending Chrome/114 in the UA header while the real browser engine was version 146. This mismatch was the trigger.
How the Mismatch Happens
When you hardcode a User-Agent string, it freezes at whatever version you copied. But Chrome auto-updates silently in the background. Over time, the gap between your hardcoded version and the actual browser version widens.
| Signal | Hardcoded UA Sends | Actual Chrome Sends |
|---|---|---|
| User-Agent header | Chrome/114.0.0.0 | Chrome/146.0.0.0 |
| Sec-CH-UA header (Client Hints) | Not set (or missing) | "Chromium";v="146" |
| TLS JA3 fingerprint | Chrome 146 pattern | Chrome 146 pattern |
The critical point: TLS fingerprints and Client Hints cannot be faked by changing the UA string alone. They come from the actual browser binary. So the UA says 114, but the TLS handshake screams 146. No real user would have this combination.
Why Bot Detection Catches Version Mismatch
Modern bot detection systems don't rely on a single signal. They cross-reference multiple browser fingerprint layers:
TLS JA3/JA4 Fingerprinting
During the TLS handshake, the browser sends a specific set of cipher suites, extensions, and elliptic curves. This combination forms a fingerprint (JA3 hash) that's unique to each browser version. Chrome 114 and Chrome 146 produce different JA3 hashes. When the UA claims 114 but the TLS fingerprint matches 146, it's an immediate red flag.
Client Hints (Sec-CH-UA)
Chrome 89+ sends Sec-CH-UA headers automatically. These headers report the real browser brand and version. Even if you override the User-Agent string, Client Hints still report the actual Chrome version. A UA saying 114 alongside a Sec-CH-UA: "Chromium";v="146" header is a contradiction that no legitimate browser would produce.
JavaScript Engine Behavior
The V8 engine version differs between Chrome releases. Bot detection scripts running in the page can probe JavaScript behavior that varies by engine version, creating yet another data point that exposes the mismatch.
When the UA claims Chrome 114 but the TLS fingerprint, Client Hints, and JS engine all say Chrome 146, the detection system concludes: this is a bot with a spoofed User-Agent. Ironically, you'd be less suspicious if you hadn't set a UA at all.
The One-Line Fix
The solution was embarrassingly simple: delete the hardcoded User-Agent line.
Before (Detected)
options.add_argument(
"user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/114.0.0.0 Safari/537.36" # ← time bomb
)After (Fixed)
# No user-agent override — Chrome sends its real version automatically
options = uc.ChromeOptions()
# ... other options onlyAfter removing the hardcoded UA, CAPTCHA frequency dropped dramatically. Combined with undetected-chromedriver, CAPTCHAs disappeared entirely. The version mismatch had been the primary detection trigger.
Why Hardcoded UA Is a Time Bomb
Chrome auto-updates by default. You don't notice when it goes from 145 to 146, but your hardcoded UA string stays at whatever version you pasted months or years ago. The longer the code runs, the bigger the gap becomes.
| Approach | Pros | Cons | Recommendation |
|---|---|---|---|
| Hardcoded UA | Simple code | Version mismatch after Chrome updates | Avoid |
| No UA override | Always matches real Chrome version perfectly | None | Recommended |
| Dynamic version lookup | Always current UA | Added complexity | Overkill for most cases |
If you absolutely must set a custom UA (for example, to emulate a mobile device), read the actual Chrome version at runtime and construct the UA string dynamically. Never paste a static version number.
Practical Checklist for Selenium Stealth
- Remove all hardcoded User-Agent strings — let Chrome send its real UA automatically
- Use undetected-chromedriver — it patches common Selenium detection vectors beyond just the UA
- Don't override Sec-CH-UA — Client Hints should match the real browser
- Keep Chrome updated — or pin a specific version and match all fingerprints to that version
- Test with bot detection checkers — sites like bot.sannysoft.com show what your browser leaks
- If you must set UA, read the version dynamically:
import subprocess
import re
# Read actual Chrome version
result = subprocess.run(['google-chrome', '--version'], capture_output=True, text=True)
version = re.search(r'(\d+\.\d+\.\d+\.\d+)', result.stdout).group(1)
ua = f"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{version} Safari/537.36"
options.add_argument(f"user-agent={ua}")Summary
- Hardcoding a Chrome User-Agent version is a time bomb — Chrome auto-updates break the match
- Bot detection cross-references UA with TLS fingerprint, Client Hints, and JS engine behavior
- Version mismatch (UA says 114, everything else says 146) is an immediate bot signal
- The fix is simple: don't set a custom UA — Chrome sends the correct one automatically
- If you must customize the UA, read the real Chrome version at runtime
- Combine with undetected-chromedriver for comprehensive Selenium stealth
The irony of User-Agent spoofing is that trying to look more human can make you look more like a bot. When your UA contradicts every other browser fingerprint, detection systems don't see a human — they see a script that's trying too hard.