Skip to content

[voice-call] Telnyx inbound calls answer and greet, but bot never replies — auto-response loop missing for webhook call.speech path #79118

Description

@dvy

Hi — following up on #58231 (which fixed Telnyx call.initiated answering — thank you). With that fix in place I now hit a different but related symptom on the same provider: the call does answer, the greeting plays, the user's transcript is captured, but the bot never speaks again. The call sits in listening until the caller hangs up.

Environment

  • OpenClaw 2026.5.6 (gateway), @openclaw/[email protected] (plugin — post-Telnyx inbound calls reach webhook but are never answered in voice-call plugin #58231 fix)
  • Provider: telnyx (Call Control v2)
  • Webhook: Cloudflare Tunnel → 127.0.0.1:3334/voice/webhook
  • Number: regulated DE DID, BNetzA-cleared, Telnyx status active
  • Inbound policy: allowlist
  • streaming.enabled: false (Telnyx provider has no streaming_start action in this build)
  • realtime.enabled: false (correctly schema-rejected for non-Twilio providers)
  • Per-number config: inboundGreeting set (German); responseSystemPrompt set

Observed

Two real inbound calls from the allowlisted caller, identical failure pattern:

step callA c8031756… callB c47509f2…
call.initiated answered
greeting gather_using_speak plays ✅ "Hallo, was kann ich für dich tun?" ✅ same
transcription_start issued
call.transcription webhook arrives ✅ "Hi how are you" final ✅ "running" final
state → listening
bot reply ever produced ❌ never ❌ never
call end caller hangup ~50s later caller hangup ~60s later

~/.openclaw/voice-calls/calls.jsonl records only the bot's initial greeting and the user's single transcript — no follow-up bot transcript entries.

Gateway journal for these calls logs only:

[voice-call] Inbound call accepted: <e164> is in allowlist
[voice-call] Created inbound call record: <callId> from <e164>
[voice-call] Starting max duration timer (300s) for call <callId>
[voice-call] Speaking initial message for call <callId> (mode: conversation)

No [voice-call] Auto-responding to inbound call … line ever appears, even with mode: conversation.

Root cause

In dist/runtime-entry-DFzuGKLG.js, two paths produce a call.speech event:

Path 1 — Twilio Media Streams onTranscript callback (~L2447):

this.manager.processEvent(event);
const callMode = call.metadata?.mode;
if (call.direction === "inbound" || callMode === "conversation")
    this.handleInboundResponse(call.callId, transcript).catch(...);

Path 2 — generic webhook call.speech handler in processEvent (used by Telnyx and any non-streaming provider):

case "call.speech":
    if (event.isFinal) {
        const hadWaiter = ctx.transcriptWaiters.has(call.callId);
        const resolved = resolveTranscriptWaiter(ctx, call.callId, event.transcript, event.turnToken);
        if (hadWaiter && !resolved) { /* warn */ break; }
        addTranscriptEntry(call, "user", event.transcript);
    }
    transitionState(call, "listening");
    break;

Path 2 records the transcript and transitions state but never invokes handleInboundResponse. grep over the entire plugin bundle shows exactly one caller of handleInboundResponse, and it is path 1.

So on Telnyx (and presumably Plivo): the carrier's webhook-based transcript event is parsed correctly (see dist/telnyx-jjBE8boz.js case "call.transcription"type: "call.speech"), the transcript gets persisted, but the auto-response never fires because the only callsite is gated behind the Twilio Media Streams socket.

Repro

  1. Configure plugin with provider: "telnyx", valid connectionId + publicKey, inbound allowlist, inboundGreeting, optional per-number responseSystemPrompt.
  2. Call the Telnyx number from an allowlisted caller.
  3. Wait for greeting, speak a sentence in response.
  4. Observe: greeting plays, transcript persists, no bot reply spoken; call sits until caller hangs up.

Suggested fix sketch

Mirror the path-1 callsite inside the generic call.speech handler, gated on the absence of an active turn waiter so programmatic continueCall flows aren't double-handled. Roughly:

case "call.speech":
    if (event.isFinal) {
        const hadWaiter = ctx.transcriptWaiters.has(call.callId);
        const resolved = resolveTranscriptWaiter(ctx, call.callId, event.transcript, event.turnToken);
        if (hadWaiter && !resolved) { /* warn */ break; }
        addTranscriptEntry(call, "user", event.transcript);
        const callMode = call.metadata?.mode;
        if (!hadWaiter && (call.direction === "inbound" || callMode === "conversation")) {
            // emit to a runtime-level handler that calls handleInboundResponse
            ctx.onAutoResponse?.(call.callId, event.transcript);
        }
    }
    transitionState(call, "listening");
    break;

(Or a direct provider-agnostic equivalent of handleInboundResponse(callId, transcript).catch(...), depending on how the runtime/manager scope is set up.)

#58231 wired up step 1 of inbound (answer the call). This issue is step 3 (after the user speaks, generate and speak the reply on the same webhook channel).

Happy to test a patch against the same Telnyx setup.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions