[BUG] Voice dictation fails ("unstructured error frame", WS close 1008 Invalid authorization) when authenticated via CLAUDE_CODE_OAUTH_TOKEN — voice WebSocket opened without Authorization header

Open 💬 0 comments Opened Jun 10, 2026 by codesections

Environment

  • Claude Code v2.1.170 (native installer), Linux x86-64 (Ubuntu, kernel 6.17)
  • Claude Max subscription
  • Authenticated via the CLAUDE_CODE_OAUTH_TOKEN environment variable

Bug

With CLAUDE_CODE_OAUTH_TOKEN set, voice dictation always fails immediately with:

Voice stream error: unstructured error frame (keys: type, error, request_id)

Unsetting CLAUDE_CODE_OAUTH_TOKEN (falling back to ~/.claude/.credentials.json auth) makes voice dictation work normally. Inference is unaffected in both cases — only the voice stream fails.

Debug log (--debug)

[DEBUG] [voice_stream] Connecting to wss://api.anthropic.com/api/ws/speech_to_text/voice_stream?encoding=linear16&sample_rate=16000&channels=1&endpointing_ms=300&utterance_end_ms=1000&language=en&use_conversation_engine=true&stt_provider=deepgram-nova3
[DEBUG] [voice_stream] WebSocket connected
[DEBUG] [voice_stream] Sending initial KeepAlive
[DEBUG] [voice] onReady: flushing 5 buffered chunks (6134 bytes) as 1 coalesced frame(s)
[DEBUG] [voice_stream] Sending audio chunk: 6134 bytes
[DEBUG] [voice_stream] Message received (214 chars)
[DEBUG] [voice_stream] Server error: unstructured error frame (keys: type, error, request_id)
[DEBUG] [voice] early voice_stream error (pre-transcript), retrying once: unstructured error frame (keys: type, error, request_id)
[DEBUG] [voice_stream] WebSocket closed: code=1008 reason="Invalid authorization"

Root-cause evidence: the WebSocket is opened with no Authorization header

I reproduced the connection with a minimal raw WebSocket client to isolate the auth behavior:

  1. Connecting to the same voice_stream URL with Authorization: Bearer <oauth access token> (the token from ~/.claude/.credentials.json, same account): handshake succeeds, KeepAlive + audio + CloseStream are accepted, and the server closes cleanly with code 1000. So the account/token works for the speech-to-text service.
  1. Connecting with no Authorization header at all: handshake still succeeds (101), and after the first frames the server sends exactly the failure signature from the debug log:
{"type":"error","error":{"type":"permission_error","message":"Invalid authorization","details":{"error_visibility":"user_facing","error_code":"account_session_invalid"}},"request_id":"req_..."}
CLOSE code=1008 reason='Invalid authorization'

That error frame has exactly the keys type, error, request_id reported by the client ("unstructured error frame"), and the same 1008 close. So when CLAUDE_CODE_OAUTH_TOKEN is the auth source, the voice stream appears to open its WebSocket without attaching any credential, rather than forwarding the env token.

Expected behavior

Either of:

  • Forward the CLAUDE_CODE_OAUTH_TOKEN credential to the voice WebSocket (it's a Claude.ai OAuth token, so dictation should presumably be supported), or
  • If env-token auth is intentionally unsupported for dictation, gate the feature with the documented "Voice mode requires a Claude.ai account. Please run /login to sign in." message instead of connecting unauthenticated and surfacing "unstructured error frame".

Relatedly, it would help if the client surfaced the server error frame's error.message / error_code ("Invalid authorization" / account_session_invalid) instead of "unstructured error frame (keys: type, error, request_id)" — the actual cause was only visible at the WebSocket level.

Workaround

Unset CLAUDE_CODE_OAUTH_TOKEN and authenticate via /login (credentials file).

View original on GitHub ↗