Voice mode: WebSocket blocked by Cloudflare bot challenge (403 cf-mitigated)

Status Fixed / completed
Maintainer reply None cached
Activity 4 comments · opened Mar 13, 2026 · closed Mar 18, 2026

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

  1. Enable voice mode with /voice
  2. Hold Space to record
  3. Error appears in bottom-right: Voice connection failed. Check your network and try again.

Diagnosis

All prerequisites pass:

  • ✅ OAuth token valid (claudeAiOauth.accessToken present, 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 avfoundation records successfully)
  • ✅ Network connectivity to claude.ai works (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.

View original on GitHub ↗

4 Comments

github-actions[bot] · 7 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/33421
  2. https://github.com/anthropics/claude-code/issues/33383
  3. https://github.com/anthropics/claude-code/issues/30664

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

ertbAl · 7 months ago

Same issue on Windows. Confirmed the WebSocket returns 403 cf-mitigated: challenge from Cloudflare — tested directly with Node.js.

System info:

  • OS: Windows 11 Pro Build 26200
  • CPU: Intel Core i5-1135G7 @ 2.40GHz (11th Gen)
  • RAM: 15.8 GB
  • Terminal: Windows Terminal 1.23.20211.0
  • Node.js: v22.16.0
  • Claude Code: 2.1.76 (npm global install)
  • Shell: bash (Git for Windows)

Diagnosis:

  • Audio native module (x64-win32/audio-capture.node) loads fine ✅
  • Microphone authorization status: 3 (authorized) ✅
  • OAuth token: valid ✅
  • Direct WebSocket test to wss://claude.ai/api/ws/speech_to_text/voice_stream → HTTP 403 Forbidden with Cloudflare JS challenge page ❌

This is purely a server-side Cloudflare WAF issue. No client-side workaround possible. Needs Anthropic to allowlist WebSocket upgrade requests from the CLI User-Agent on this endpoint.

PiotrStrzelecki-TomTom · 7 months ago

+1 — same issue on Linux.

  • OS: Ubuntu, kernel 6.8.0-101-generic
  • Claude Code: v2.1.76 (npm global)
  • Node.js: v22.11.0
  • Auth: OAuth (Claude Max)
  • Audio: PipeWire, multiple mics detected (all working)

Network capture confirms TCP+TLS to 34.149.66.137:443 (GCP) succeeds — connection reaches ESTAB but fails at application layer. Error is purely Cloudflare WebSocket rejection.

github-actions[bot] · 6 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.