When a call is answered in silence
A call that connects and then plays nothing leaves the same trace as a caller who hung up immediately. On 2026-09-07 five inbound calls sat silent and were recorded as abandonment. The cause was a greeting the process could not synthesise. The call now ends deliberately, posts engine_error {at: greeting}, and is written down as something that was never a conversation.
Two very different calls, one indistinguishable record#
A caller who rings, hears nothing and hangs up after four seconds produces a short answered call with no caller speech. A caller who rings, is answered by a process that cannot speak, and hangs up after four seconds produces exactly the same thing. One is ordinary; the other is a total failure of the product, and for a while both were summarised as *the caller hung up before speaking*.
A failure that files itself under a benign heading is worse than a loud one. Nobody investigates a hang-up. The five calls were only found because a restart pattern was being looked at for another reason.
What was actually happening#
The SDK runs each call in its own process and calls the prewarm hook in every idle process. Each of those processes asked the text-to-speech model for the same greeting sentence, under the same cache key, in the same millisecond. The model's per-minute quota answered one of them 429 — and a refused synthesis was treated as a permanent answer, so that process had no greeting for the rest of its life. Whichever call landed on it was answered in silence.
A second, quieter contributor sat underneath: process initialisation had a default bound of 10 s, which was *shorter* than the warm-up's own budget. The warm-up could be killed for being slow before it had a chance to fail for being refused.
The fix has two halves#
Stop the stampede. Warm-up now spreads processes over
WARM_STAGGER_Sand retries a refused synthesisWARM_RETRIEStimes with backoff when the refusal is one that passes — 429 and 5xx, never a bad key.Result
refusedis counted separately fromfailed, so a quota problem and a broken credential no longer look alike. The worker asks forinitialize_process_timeout45 s against aWARM_TIMEOUT_Sof 30 s.Make an unsayable greeting end the call rather than start one.
_sayasks once more when the refusal passes; if the line still cannot be spoken on a model that takes no generated line, the worker postsengine_error {at: greeting}and ends withhangup_by=engine.Result The engine writes
facts.silent_failurefrom that event,voice.no_conversationsays so, and the outcome isnot_reached— a call that never rang a conversation into existence.
The warm-up itself is the reason the greeting is worth this much trouble: a cached line starts in 0 ms against 3.0 s for live synthesis, and greeting.prewarmed on the event says which of the two the caller got.
The vocabulary that keeps the cases apart#
no_answer- It rang and nobody picked up. An ordinary outcome.
not_reached- It never rang — the dial, the dispatch or the worker failed. Not the caller's doing.
silent_failure- It was answered and nothing could be said. Written from the
engine_errorevent, not inferred. line_off/after_hours- A deliberate refusal: the closed-line message was spoken in the business's own name, then the call ended.
hangup_by- One of caller, model, owner, engine, carrier, agent. Before this, every ending that was not the caller's read *agent*.
_capture_lead creates no lead from a call that was never a conversation, which stops a run of failures from quietly manufacturing prospects. The refusal path is equally deliberate: a switched-off line used to be a bare 409 that the worker answered by deleting the room, so the caller heard the call connect and drop and no Call row existed at all. It now returns a refusal brief carrying closed_line_text — deterministic, warmed alongside the greetings, and with no opening hours in it.
What is still only partly covered#
voice_engine.line_healthcatches the adjacent failure — a worker that has not checked in, so an inbound call reaches nothing at all — which the Phone screen once reported as *ready*. It drains by itself when the cause clears.- Silence with a cause outside the process, such as a one-way audio path on the carrier leg, is not covered by this event and would still read as a short call.
- The five calls are the only counted instance. No ongoing rate of silent answers is measured here: UNKNOWN.
- The guard depends on the greeting being a known utterance. A model that could generate its own opening would need a different signal that speech began.
Questions#
Why end the call instead of continuing without a greeting?
Because the caller has no way to know a conversation started. Continuing produces a call where the first thing a stranger hears is silence, then possibly a reply to something they said into that silence. Ending immediately with the failure on the record is honest, and it makes the fault countable.
How would I notice this on my own line?
Compare the carrier's own call log against the call records. A completed, charged call on their side with nothing or a four-second nothing on yours is the signature. That comparison is the fastest audit available on any telephony integration and it has found more than one defect here.
Does a refused greeting count against the caller?
No. The outcome is not_reached, no lead is captured, and the ending is attributed to the engine rather than to the caller — which is the whole point of hangup_by having six possible values instead of defaulting to one.