Skip to content

fix(voice-call): preserve live Twilio streams in stale reaper#90620

Closed
sahibzada-allahyar wants to merge 1 commit into
openclaw:mainfrom
sahibzada-allahyar:fastino-79121-twilio-stale-reaper
Closed

fix(voice-call): preserve live Twilio streams in stale reaper#90620
sahibzada-allahyar wants to merge 1 commit into
openclaw:mainfrom
sahibzada-allahyar:fastino-79121-twilio-stale-reaper

Conversation

@sahibzada-allahyar

@sahibzada-allahyar sahibzada-allahyar commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #79121.

What changed

  • Keep the voice-call stale reaper from ending calls that are actively in live conversation states (speaking / listening) even when answeredAt is absent.
  • Add a regression test for the inbound Twilio shape where a live speaking call has no answeredAt and should not be reaped.

Real behavior proof

Behavior addressed: The stale-call reaper no longer ends a live Twilio-style voice call that is in speaking state with no answeredAt. This matches inbound Twilio media-stream conversations where speech/transcription activity can advance the call state even when no separate call.answered event populated answeredAt.

Real environment tested: macOS local OpenClaw checkout. Base proof used clean origin/main at 1a3ce7c2 in /private/tmp/openclaw-79121-base. Fix proof used branch fastino-79121-twilio-stale-reaper at e1cd850a in /Users/allahyar/Documents/fastino-tasks/openclaw-tui-79121. Package manager was pnpm v11.2.2.

Exact steps or command run after the patch: Ran a runtime probe that imports the real startStaleCallReaper, intercepts the interval callback, sets Date.now() to 2026-02-16T00:00:00Z, and gives the manager one active call: { callId: "twilio-live-speaking", startedAt: now - 120000, state: "speaking" } with no answeredAt. I ran the same probe on clean origin/main and this branch. I also ran focused stale-reaper tests, the full voice-call extension suite, formatting, lint, and whitespace checks.

Evidence after fix: Before the fix, clean origin/main reaped the live speaking call:

$ node --import tsx --input-type=module -e 'const callbacks=[]; const originalSetInterval=globalThis.setInterval; const originalClearInterval=globalThis.clearInterval; const originalNow=Date.now; globalThis.setInterval=(fn)=>{callbacks.push(fn); return 1}; globalThis.clearInterval=()=>{}; Date.now=()=>Date.parse("2026-02-16T00:00:00Z"); const { startStaleCallReaper } = await import("./extensions/voice-call/src/webhook/stale-call-reaper.ts"); const ended=[]; const manager={ getActiveCalls:()=>[{ callId:"twilio-live-speaking", startedAt: Date.now() - 120000, state:"speaking" }], endCall: async (id)=>{ ended.push(id); } }; const stop=startStaleCallReaper({ manager, staleCallReaperSeconds:60 }); callbacks[0](); await Promise.resolve(); console.log(JSON.stringify({ worktree:"origin/main", callbacks:callbacks.length, stopType:typeof stop, ended })); globalThis.setInterval=originalSetInterval; globalThis.clearInterval=originalClearInterval; Date.now=originalNow;'
[voice-call] Reaping stale call twilio-live-speaking (age: 120s, state: speaking)
{"worktree":"origin/main","callbacks":1,"stopType":"function","ended":["twilio-live-speaking"]}

After the fix, the same runtime probe on this branch leaves the live speaking call alone:

$ node --import tsx --input-type=module -e 'const callbacks=[]; const originalSetInterval=globalThis.setInterval; const originalClearInterval=globalThis.clearInterval; const originalNow=Date.now; globalThis.setInterval=(fn)=>{callbacks.push(fn); return 1}; globalThis.clearInterval=()=>{}; Date.now=()=>Date.parse("2026-02-16T00:00:00Z"); const { startStaleCallReaper } = await import("./extensions/voice-call/src/webhook/stale-call-reaper.ts"); const ended=[]; const manager={ getActiveCalls:()=>[{ callId:"twilio-live-speaking", startedAt: Date.now() - 120000, state:"speaking" }], endCall: async (id)=>{ ended.push(id); } }; const stop=startStaleCallReaper({ manager, staleCallReaperSeconds:60 }); callbacks[0](); await Promise.resolve(); console.log(JSON.stringify({ worktree:"fastino-79121-twilio-stale-reaper", callbacks:callbacks.length, stopType:typeof stop, ended })); globalThis.setInterval=originalSetInterval; globalThis.clearInterval=originalClearInterval; Date.now=originalNow;'
{"worktree":"fastino-79121-twilio-stale-reaper","callbacks":1,"stopType":"function","ended":[]}

Additional verification:

$ pnpm test:extension voice-call -t 'stale call reaper'
Test Files  2 passed | 48 skipped (50)
Tests  6 passed | 443 skipped (449)

$ pnpm test:extension voice-call
Test Files  50 passed (50)
Tests  449 passed (449)

$ pnpm exec oxfmt --check extensions/voice-call/src/webhook/stale-call-reaper.ts extensions/voice-call/src/webhook.test.ts
All matched files use the correct format.

$ pnpm exec oxlint extensions/voice-call/src/webhook/stale-call-reaper.ts extensions/voice-call/src/webhook.test.ts --report-unused-disable-directives-severity error
# passed with no output

$ git diff --check
# passed with no output

Observed result after fix: Before the fix, a 120-second speaking call with no answeredAt was logged and ended as stale. After the fix, the same call is recognized as live conversation activity and is not ended. Existing stale cleanup behavior for non-live stale calls remains covered by the full voice-call extension suite.

What was not tested: I did not place a live Twilio phone call. The exercised behavior is the local production stale-reaper code path that caused #79121, using a runtime manager probe and the voice-call extension tests.

Platforms tested

  • macOS local checkout

@openclaw-barnacle openclaw-barnacle Bot added channel: voice-call Channel integration: voice-call size: XS triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 5, 2026
@clawsweeper

clawsweeper Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Codex review: passed. Reviewed June 5, 2026, 10:12 PM ET / 02:12 UTC.

Summary
The PR updates the voice-call stale reaper to skip live speaking and listening calls without answeredAt, plus a regression test for an inbound Twilio-style speaking call.

PR surface: Source +16, Tests +15. Total +31 across 2 files.

Reproducibility: yes. Source review and the contributor's terminal before/after probe show current main reaps a stale-threshold speaking call without answeredAt, while the branch leaves it active.

Review metrics: none identified.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Risk before merge

  • [P1] No live Twilio phone call was placed; the supplied proof exercises the production stale-reaper helper with a simulated manager plus focused/full voice-call tests.

Maintainer options:

  1. Decide the mitigation before merge
    Land the narrow stale-reaper guard so live conversation states are preserved while existing cleanup for genuinely stuck non-live calls remains covered.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] No repair lane is needed; the automerge path can gate this exact head on required checks and mergeability.

Security
Cleared: The diff only changes voice-call stale-reaper logic and tests; it does not add dependencies, workflow execution, credential handling, or supply-chain surface.

Review details

Best possible solution:

Land the narrow stale-reaper guard so live conversation states are preserved while existing cleanup for genuinely stuck non-live calls remains covered.

Do we have a high-confidence way to reproduce the issue?

Yes. Source review and the contributor's terminal before/after probe show current main reaps a stale-threshold speaking call without answeredAt, while the branch leaves it active.

Is this the best way to solve the issue?

Yes. Guarding the owner-owned stale reaper against live conversation states is narrower than disabling cleanup or changing user-facing config defaults, and it preserves existing stale-call cleanup behavior.

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 3a2f54e6a866.

Label changes

Label changes:

  • add status: 🚀 automerge armed: This PR is in ClawSweeper's automerge lane. Sufficient (terminal): The PR body includes exact terminal before/after output for the production stale-reaper helper and focused/full voice-call test output, which is sufficient for this non-visual bug fix.
  • remove status: 👀 ready for maintainer look: Current PR status label is status: 🚀 automerge armed.

Label justifications:

  • P1: The PR fixes a linked user-facing voice-call workflow where an active Twilio conversation can be terminated mid-call.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 🚀 automerge armed: This PR is in ClawSweeper's automerge lane. Sufficient (terminal): The PR body includes exact terminal before/after output for the production stale-reaper helper and focused/full voice-call test output, which is sufficient for this non-visual bug fix.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes exact terminal before/after output for the production stale-reaper helper and focused/full voice-call test output, which is sufficient for this non-visual bug fix.
Evidence reviewed

PR surface:

Source +16, Tests +15. Total +31 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 17 1 +16
Tests 1 15 0 +15
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 32 1 +31

What I checked:

Likely related people:

  • steipete: GitHub file history shows recent commits touching the stale-reaper and webhook test files, including voice-call runtime documentation and large-extension source documentation. (role: recent area contributor; confidence: medium; commits: 6868cde4d45f, d8b5e22e8bfd; files: extensions/voice-call/src/webhook/stale-call-reaper.ts, extensions/voice-call/src/webhook.test.ts)
  • vincentkoc: GitHub file history shows recent voice-call manager/event work adjacent to the state and webhook behavior this PR relies on. (role: adjacent voice-call contributor; confidence: medium; commits: c8cee2dce4e3; files: extensions/voice-call/src/manager/events.ts, extensions/voice-call/src/manager/state.ts)
  • Ayaan Zaidi: The local shallow/grafted checkout attributes the current voice-call files to commit 61d121f, so this is a low-confidence routing signal from the available local history. (role: current checkout provenance; confidence: low; commits: 61d121f1caa2; files: extensions/voice-call/src/webhook/stale-call-reaper.ts, extensions/voice-call/src/manager/events.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P1 High-priority user-facing bug, regression, or broken workflow. labels Jun 5, 2026
@Takhoffman

Copy link
Copy Markdown
Contributor

@clawsweeper automerge

@clawsweeper

clawsweeper Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

🦞🔧
ClawSweeper saw the passing review, but the PR needs another repair pass before merge.

Source: clawsweeper[bot]
Feedback: structured ClawSweeper verdict: pass (sha=e1cd850a9afaffb3ecc968ea123467a2cc6584fa); failed required checks before automerge: Real behavior proof:CANCELLED
Action: repair worker queued. Run: https://github.com/openclaw/clawsweeper/actions/runs/27049749552
Model: gpt-5.5

I will update this PR branch, or open a safe credited replacement, if the repair worker finds a narrow CI fix.

Automerge progress:

  • 2026-06-06 02:07:15 UTC review queued e1cd850a9afa (queued)
  • 2026-06-06 02:12:27 UTC review passed e1cd850a9afa (structured ClawSweeper verdict: pass (sha=e1cd850a9afaffb3ecc968ea123467a2cc658...)

@clawsweeper clawsweeper Bot added clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge status: 🚀 automerge armed This PR is in ClawSweeper's automerge lane. and removed status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 6, 2026
@clawsweeper

clawsweeper Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper 🐠 reef update

Thanks for the work here. ClawSweeper could not write to the source branch, so it opened a replacement PR rather than letting the fix drift. attribution still points back here.

Why replacement: ClawSweeper could not update the source PR branch directly; GitHub did not grant sufficient push rights to the bot for that branch.
Replacement PR: #90812
Why close: this run explicitly closes the superseded source PR after the credited replacement PR is open, so review continues in one place.
Closing this one because the run was configured to close superseded source PRs after opening the replacement.
Contributor credit is carried into the replacement PR body and release-note context.
Co-author credit kept:

fish notes: model gpt-5.5, reasoning high; reviewed against 060f3de.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: voice-call Channel integration: voice-call clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: XS status: 🚀 automerge armed This PR is in ClawSweeper's automerge lane.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: voice-call stale reaper ends active Twilio conversation after ~120s

2 participants