Voice mode: WebSocket blocked by Cloudflare bot challenge (403 cf-mitigated)
Summary
Voice mode (/voice) fails with "Voice connection failed. Check your network and try again." The root cause is Cloudflare blocking the Node.js WebSocket upgrade to wss://claude.ai/api/ws/speech_to_text/voice_stream with a 403 cf-mitigated: challenge response.
Environment
- Claude Code: v2.1.74
- Plan: Claude Max (OAuth)
- OS: macOS 15 (Darwin 25.2.0), Apple Silicon
- Node.js: v22.18.0
- Region: Brazil (Cloudflare PoP: GRU — São Paulo)
- Terminal: Apple Terminal
Steps to Reproduce
- Enable voice mode with
/voice - Hold Space to record
- Error appears in bottom-right:
Voice connection failed. Check your network and try again.
Diagnosis
All prerequisites pass:
- ✅ OAuth token valid (
claudeAiOauth.accessTokenpresent, not expired) - ✅ Feature gates enabled (
tengu_amber_quartz✅) - ✅ Audio dependencies installed (
ffmpeg,sox,rec) - ✅ Microphone permissions granted (Terminal.app authorized in TCC)
- ✅ Audio recording works (
ffmpeg -f avfoundationrecords successfully) - ✅ Network connectivity to
claude.aiworks (HTTPS 200 from Node.js)
The WebSocket connection is blocked by Cloudflare:
// Node.js ws library — FAILS
const ws = new WebSocket('wss://claude.ai/api/ws/speech_to_text/voice_stream?...');
// → 403 with header: cf-mitigated: challenge, server: cloudflare
# Python websockets — SUCCEEDS (different TLS fingerprint)
async with websockets.connect(uri, additional_headers=headers) as ws:
# → Connected OK, KeepAlive works
The cf-mitigated: challenge header confirms Cloudflare's bot detection is fingerprinting the TLS/HTTP characteristics of Node.js and triggering a JavaScript challenge, which a WebSocket client cannot solve.
Cloudflare Ray IDs
9dbd0560eb9c5f28(HTTPS test)9dbd12adcb68c27c(WebSocket test — blocked)
Both routed through GRU (São Paulo). This may be region-specific — Cloudflare WAF rules can differ by PoP.
Expected Behavior
The voice WebSocket should connect successfully, as the OAuth token is valid and the endpoint is reachable.
Suggested Fix
Adjust Cloudflare WAF/bot rules to allowlist WebSocket upgrade requests to /api/ws/speech_to_text/voice_stream from Claude Code's User-Agent (claude-code/*), or configure a Cloudflare bypass rule for this endpoint.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗