What happens
With attach_stream="a-leg" (#762, PR #982), place_call() issues Calls.create and then waits on _stream_connected for timeout (default 120s). If the callee never answers — Twilio marks the call busy, no-answer, failed or canceled within seconds — the adapter still sits for the full timeout before _abandon_originated_call hangs up and raises TimeoutError.
Observed twice on 2026-09-09 while producing the proof: Twilio returned busy at +6s, the adapter raised at +120s.
Proposal
Fail fast as soon as Twilio reports a terminal non-answered status, and surface it as a distinct error (CallNotAnsweredError(status=...)) rather than TimeoutError. Two ways to learn the status, either is fine:
- Pass
status_callback=<public_base_url>/twilio/status with status_callback_event=["answered","completed"] on Calls.create and have the server set an event; the a-leg path already owns the tunnel + server.
- Poll
calls(sid).fetch().status every ~2s while waiting on _stream_connected.
Keep TimeoutError for the case where the call is in-progress but the stream never connects (tunnel/WS problem).
Same for the JS adapter (placeCall / _abandonOriginatedCall).
🤖 Generated with Claude Code
https://claude.ai/code/session_01Tc6K2QfkvcWiasy8ane6EG
What happens
With
attach_stream="a-leg"(#762, PR #982),place_call()issuesCalls.createand then waits on_stream_connectedfortimeout(default 120s). If the callee never answers — Twilio marks the callbusy,no-answer,failedorcanceledwithin seconds — the adapter still sits for the full timeout before_abandon_originated_callhangs up and raisesTimeoutError.Observed twice on 2026-09-09 while producing the proof: Twilio returned
busyat +6s, the adapter raised at +120s.Proposal
Fail fast as soon as Twilio reports a terminal non-answered status, and surface it as a distinct error (
CallNotAnsweredError(status=...)) rather thanTimeoutError. Two ways to learn the status, either is fine:status_callback=<public_base_url>/twilio/statuswithstatus_callback_event=["answered","completed"]onCalls.createand have the server set an event; the a-leg path already owns the tunnel + server.calls(sid).fetch().statusevery ~2s while waiting on_stream_connected.Keep
TimeoutErrorfor the case where the call isin-progressbut the stream never connects (tunnel/WS problem).Same for the JS adapter (
placeCall/_abandonOriginatedCall).🤖 Generated with Claude Code
https://claude.ai/code/session_01Tc6K2QfkvcWiasy8ane6EG