Skip to content

[Bug]: voice-call per-phone sessions use raw voice:* keys that conflict with gateway session canonicalization on restart #80064

Description

@xueqingli1

What happened?

Voice Call sessionScope: "per-phone" can lose repeat-caller continuity across Gateway restarts because the voice-call plugin resolves callers to raw voice:* session keys while Gateway startup migration canonicalizes those keys into agent:<agentId>:voice:*.

This is a Voice Call regression/conflict after the per-phone/per-call sessionScope work in
#45280

It is tangentially related to #63005, but this issue is narrower: the official @openclaw/voice-call plugin should use the post-migration/canonical session key format, or otherwise coordinate with the migration layer, so per-phone mode keeps its documented memory behavior.

Environment

  • OpenClaw 2026.5.7 (eeef486)
  • @openclaw/voice-call 2026.5.7
  • Ubuntu host, global npm install, Gateway service
  • Voice Call configured with:
    • agentId: "main"
    • sessionScope: "per-phone"

Observed behavior

The installed Voice Call session resolver produced raw session keys:

if (params.config.sessionScope === "per-call") return `voice:call:${params.callId}`;
const normalizedPhone = params.phone?.replace(/\D/g, "");
return normalizedPhone ? `voice:${normalizedPhone}` : `voice:${params.callId}`;

Gateway startup then repeatedly logged canonicalization of those raw keys:

gateway: canonicalized orphaned session keys
Canonicalized 1 orphaned session key(s) in .../agents/main/sessions/sessions.json
Canonicalized 2 orphaned session key(s) in .../agents/main/sessions/sessions.json

After startup migration, the session store contains canonical keys like:

agent:main:voice:<normalizedPhone>

but Voice Call inbound resolution still asks for:

voice:<normalizedPhone>

Expected behavior

sessionScope: "per-phone" should keep a stable session for the same caller across Gateway restarts.

Voice Call should resolve per-phone and per-call sessions using the same canonical session key format that survives Gateway migration, for example:

agent:<agentId>:voice:<normalizedPhone>
agent:<agentId>:voice:call:<callId>

Local workaround

Patching the installed voice-call resolver to emit agent-scoped keys stopped the mismatch locally:

 function resolveVoiceCallSessionKey(params) {
   const explicit = params.explicitSessionKey?.trim();
   if (explicit) return explicit;
-  if (params.config.sessionScope === "per-call") return `voice:call:${params.callId}`;
+  const agentId = params.config.agentId?.trim() || "main";
+  const voicePrefix = `agent:${agentId}:voice`;
+  if (params.config.sessionScope === "per-call") return `${voicePrefix}:call:${params.callId}`;
   const normalizedPhone = params.phone?.replace(/\D/g, "");
-  return normalizedPhone ? `voice:${normalizedPhone}` : `voice:${params.callId}`;
+  return normalizedPhone ? `${voicePrefix}:${normalizedPhone}` : `${voicePrefix}:${params.callId}`;
 }

Impact

  • Breaks or risks breaking the per-phone memory guarantee introduced by Feature request: per-call session scope for voice-call plugin #45280.
  • Shows up after Gateway restarts, because startup migration rewrites/prunes raw session keys.
  • Can split the same caller between voice:* and agent:<agentId>:voice:* records.
  • Severity is high for phone assistants that rely on caller continuity.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions