← Back to PRs

#9760: fix(voice-call): discard stale calls on plugin restart

by leszekszpunar open 2026-02-05 16:56 View on GitHub →
channel: voice-call stale
Fixes #9707 ## Problem When the voice-call plugin restarts (e.g. after a crash or gateway restart), it reloads active calls from `calls.jsonl`. However, calls that were active before the crash but have since expired (exceeded `maxDurationSeconds`) are still restored as active. These stale calls count toward `maxConcurrentCalls`, blocking new calls from being placed until the stale entries are manually cleared. ## Solution - Added optional `maxAgeMs` parameter to `loadActiveCallsFromStore()` so it can skip calls older than a given threshold during recovery - In `CallManager.loadActiveCalls()`, use `config.maxDurationSeconds` to compute the TTL and discard any call whose `startedAt` exceeds that window - Both the manager-level inline filter and the store-level parameter work together for defense-in-depth ## Changes - `extensions/voice-call/src/manager.ts` - skip stale calls in `loadActiveCalls()` using `maxDurationSeconds * 1000` as the age threshold - `extensions/voice-call/src/manager/store.ts` - accept `opts.maxAgeMs` in `loadActiveCallsFromStore()` and filter accordingly - `extensions/voice-call/src/manager/store.test.ts` - 5 new tests covering: empty store, non-terminal load, terminal skip, stale discard with maxAgeMs, and retention without maxAgeMs <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR updates voice-call crash recovery to avoid restoring stale active calls from `calls.jsonl`. `CallManager.loadActiveCalls()` now discards non-terminal calls whose `startedAt` is older than `config.maxDurationSeconds`, and the store helper `loadActiveCallsFromStore()` adds an optional `maxAgeMs` filter to enforce the same TTL during load. New vitest coverage exercises loading behavior and the max-age filter in the store loader. <h3>Confidence Score: 4/5</h3> - Mostly safe to merge, but TTL bypass on future timestamps should be fixed. - Change is localized and test-covered, but both recovery paths use `now - startedAt` without handling future timestamps; a single bad persisted record could still be restored and block concurrency, undermining the PR goal. - extensions/voice-call/src/manager.ts, extensions/voice-call/src/manager/store.ts <!-- greptile_other_comments_section --> <!-- /greptile_comment --> Also fixes #6721 lobster-biscuit

Most Similar PRs