Closed bug report

Default narration synthesis fails upstream WebSocket handshake403 while health is OK

John Lauer · 8d ago ·edited ·closed by John Lauer

Environment: adom-tts 1.9.56, service-tts 0.0.1, edge-tts 7.2.8, Linux container. No desktop playback requested.

On 2026-10-12 a 119-character narration request using default voice/rate failed all four automatic attempts with HTTP 500. Expected an MP3 file. No rate or voice overrides were supplied; no output audio was produced. Earlier project narration succeeded.

Repro:

adom-tts say 'The larger cooler needs more room. Here we raise the fan and reroute its wires, then check the assembly for collisions.' --out /tmp/cooling-narration.mp3 --thread adom-custom-load --desc 'Cooling assembly candidate inspection narration'

Exact diagnostic excerpt:

HTTP 500 on all 4 attempts with identical arguments (voice en-US-AndrewNeural, rate +0%, 119 chars).
edge-tts exited 1 (voice en-US-AndrewNeural, rate +0%): aiohttp.client_exceptions.WSServerHandshakeError: 403, message='Invalid response status'

The error identifies the upstream speech WebSocket handshake. adom-tts health concurrently returned {"ok":true,"service":"service-tts","version":"0.0.1","edge_tts_version":"edge-tts 7.2.8"}. Service reachability is therefore not synthesis readiness.

Please investigate upstream synthesis authorization/compatibility and make health readiness distinguish successful synthesis from process reachability. Project continues with an explicitly silent, narration-pending clip; no local speech-engine install, retry loop, shared deployment change or claimed fix.

1 Reply

John Lauer · 7d ago

The outage and the ask are both handled.

The outage: Microsoft stopped accepting speech synthesis on the host edge-tts 7.2.8 connects to (speech.platform.bing.com) on 2026-10-12, answering every WebSocket handshake with a 403. Drew's service-tts commit ad40bb0 repoints the installed edge-tts at api.msedgeservices.com (upstream rany2/edge-tts PR #491) via edge-tts-hotfix.sh, which the watchdog re-applies every cycle, so synthesis has been back since then. Your repro produces audio again on the first attempt.

The ask, shipped:

service-tts (commit after ad40bb0, deployed by the watchdog):

  • GET /health now carries ready (can it SPEAK) beside ok (does the process answer), plus a synth block with last_ok_at, last_error_at, last_error and checked_at. ready is refreshed by every real /tts/say, and by a real out-of-cache probe synthesis when nothing has been tried for 5 minutes; ?probe=1 forces one now. When ready is false the body carries a hint that names the upstream and the next step.
  • A /tts/say whose edge-tts failure names the handshake, a 403, or an unreachable host now answers 502 with upstream: true and a hint, so a client can tell "Microsoft refused us" from "this request is malformed" (which stays 400) and from a real service fault (500).

adom-tts 1.9.57:

  • adom-tts health exits 0 only when ready is true, exits 5 with the hint when the process answers but synthesis is refused, and --probe forces a fresh probe. Against an older service with no readiness field it says so instead of claiming health.
  • A say that meets an upstream refusal (502, or a body naming the handshake or a 403) says "upstream speech endpoint refused", spaces its four attempts over about 35 seconds instead of 4, and its final hint says in so many words that this is upstream of service-tts and not your arguments, to wait a few minutes, to run adom-tts health --probe, and to report it after an hour. The malformed-request and service-fault hints are unchanged.
  • The player's "Voice service" status pill reads the same ready field: "answering" only when synthesis is verified, "upstream down" with the last error otherwise.

Verified: a local service-tts with the patched edge-tts reports ready:true and adom-tts health exits 0; the same service with a fake edge-tts that fails the handshake reports ready:false with the hint, health --probe exits 5, say answers 502 upstream and the CLI prints the upstream hint after four spaced attempts. Then the live service after the watchdog picked the commit up.

Log in to reply.