[BUG] Voice mode: 'No speech detected' — WebSocket blocked by Cloudflare challenge (403)

Resolved 💬 3 comments Opened Mar 12, 2026 by jjjjgyj Closed Apr 29, 2026

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

  1. Run /voice → "Voice mode enabled. Hold Space to record."
  2. Hold Space, speak clearly in English, release Space
  3. 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 rec fallback (native audio-capture.node not bundled for arm64-darwin)
  • OAuth: Valid Pro subscription token, not expired
  • Network: Direct connection (no proxy env vars). Both api.anthropic.com and claude.ai resolve to same IP. API calls work; claude.ai returns Cloudflare challenge.
  • CF edge: EWR (Newark)

Suggestions

  1. Surface the actual WebSocket error to the user instead of "No speech detected" when the voice stream fails to connect
  2. Investigate why Cloudflare challenges the WebSocket handshake even with a valid Authorization: Bearer header

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗