[BUG] Voice mode: 'No speech detected' — WebSocket blocked by Cloudflare challenge (403)
Bug Description
Voice mode (/voice) always shows "No speech detected" even when speaking clearly in English. The root cause is that the WebSocket connection to wss://claude.ai/api/ws/speech_to_text/voice_stream is rejected with HTTP 403 by Cloudflare's managed challenge (cf-mitigated: challenge).
Steps to Reproduce
- Run
/voice→ "Voice mode enabled. Hold Space to record." - Hold Space, speak clearly in English, release Space
- Result: "No speech detected."
Root Cause
The voice stream WebSocket handshake to claude.ai is blocked by Cloudflare bot challenge. A Node.js WebSocket client cannot solve JavaScript challenges, so the connection always fails.
Verified with a standalone test using the ws library + a valid OAuth token from keychain:
Token present: true
Connecting...
HTTP 403 Forbidden
CF headers: {"cf-mitigated":"challenge","cf-ray":"....-EWR"}
Regular API calls to api.anthropic.com work fine (no Cloudflare challenge on that endpoint).
Silent error swallowing makes debugging hard
The voice code retries once on early WebSocket error, then silently drops subsequent errors when the voice stream reference is null. This means the user sees the misleading "No speech detected" instead of the actual connection error. The relevant pattern (deminified):
// onError handler:
if (!hasTranscript && state === "recording") {
if (!hasRetried) {
hasRetried = true;
voiceStream = null;
setTimeout(() => reconnect(), 250);
return;
}
if (voiceStream === null) {
return; // ← silently swallowed, no error shown to user
}
}
When finishRecording() runs, voiceStream is null → Promise.resolve() → empty transcript → "No speech detected".
Environment
- Claude Code: 2.1.73
- OS: macOS 15.4 (Darwin 25.2.0), Apple Silicon
- Node.js: v22.13.1
- Audio: SoX
recfallback (nativeaudio-capture.nodenot bundled for arm64-darwin) - OAuth: Valid Pro subscription token, not expired
- Network: Direct connection (no proxy env vars). Both
api.anthropic.comandclaude.airesolve to same IP. API calls work;claude.aireturns Cloudflare challenge. - CF edge: EWR (Newark)
Suggestions
- Surface the actual WebSocket error to the user instead of "No speech detected" when the voice stream fails to connect
- Investigate why Cloudflare challenges the WebSocket handshake even with a valid
Authorization: Bearerheader
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗