Voice mode fails with 'Voice connection failed' — Cloudflare challenge blocks WebSocket to claude.ai
Environment
- OS: Windows 10/11 (x64)
- Node.js: v20.18.1
- Claude Code: 2.1.76
- Auth: claude.ai OAuth, subscription: max
- Region: Romania (Bucharest), ISP: DIGI ROMANIA S.A. (AS8708)
Bug Description
/voice enables successfully ("Voice mode enabled. Hold Space to record."), but after recording and releasing Space, it always fails with:
Voice connection failed. Check your network and try again.
Root Cause Analysis
The voice stream WebSocket (wss://claude.ai/api/ws/speech_to_text/voice_stream) never connects because Cloudflare blocks all programmatic requests to claude.ai from this IP/region with a JavaScript challenge.
Any HTTP request to claude.ai returns:
HTTP 403
cf-mitigated: challenge
server: cloudflare
This challenge cannot be solved by a CLI/Node.js WebSocket client, so the connection never establishes. The finishRecording() function sees wsConnected=false and transcriptChars=0, triggering the "Voice connection failed" error.
What Works
| Component | Status |
|-----------|--------|
| Native audio module (audio-capture.node x64-win32) | Loads OK, 7 exports |
| OAuth authentication | loggedIn: true |
| api.anthropic.com (main API) | Works fine (no Cloudflare challenge) |
| Microphone / audio devices | 4 devices, all status OK |
| Windows Firewall | Claude rules: Allow in/out |
| DNS resolution for claude.ai | Resolves to 160.79.104.10 |
| TLS to claude.ai | TLSv1.3, certificate valid |
What Fails
- ANY programmatic request from Node.js to
claude.ai→ 403 withcf-mitigated: challenge - WebSocket upgrade to
wss://claude.ai/...→ blocked by Cloudflare before reaching server
Reproduction
# This returns 403 with cf-mitigated: challenge
curl -s -o /dev/null -w "%{http_code}" https://claude.ai
# Returns: 403
# This works fine
curl -s -o /dev/null -w "%{http_code}" https://api.anthropic.com
# Returns: 404 (expected, no auth)
Expected Behavior
Voice WebSocket should connect to claude.ai without being blocked by Cloudflare challenge, similar to how api.anthropic.com handles CLI connections.
Workaround
Using a VPN changes the IP and bypasses the Cloudflare challenge, making voice mode work. This confirms the issue is IP/region-based Cloudflare challenge policy.
Suggested Fix
Consider one of:
- Exempting the
/api/ws/speech_to_text/voice_streamendpoint from Cloudflare JavaScript challenges (requests include valid OAuth Bearer tokens) - Using a separate non-challenge-protected domain for the voice stream (similar to
api.anthropic.com) - Adding a Cloudflare bypass token/header for authenticated CLI clients
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗