← Back to PRs

#21887: fix: drop stale announce-queue items after 5-minute TTL

by John-Rood open 2026-02-20 14:01 View on GitHub →
agents size: S
## Problem When the agent is busy (long tool calls, multi-step work), subagent/cron completion announcements get queued in memory. If the agent stays busy for minutes or hours, these stale items eventually drain into the *next* conversation turn — making it appear the agent is randomly doing old work. Example: a subagent finishes at 2pm, agent is busy until 4pm, user sends a new message, and the agent starts talking about the 2pm result as if it just happened. ## Fix Add a TTL check to `scheduleAnnounceDrain` in `subagent-announce-queue.ts`. Items older than 5 minutes (`ANNOUNCE_MAX_AGE_MS`) are dropped with a log message instead of delivered. The TTL filter runs after the debounce wait and before any send — covering both `followup` and `collect` drain modes. ## Changes - `src/agents/subagent-announce-queue.ts` — TTL constant + stale item filter in drain loop - `src/agents/subagent-announce-queue.test.ts` — test confirming stale items are dropped while fresh items deliver normally ## Notes - `enqueuedAt` was already part of `AnnounceQueueItem` — no type changes needed - `drainNextQueueItem` in queue-helpers is generic/shared, so the TTL logic lives in the announce-specific drain loop - 4 existing tests pass, 1 new test added <!-- greptile_comment --> <h3>Greptile Summary</h3> Adds a 5-minute TTL to prevent stale subagent/cron completion announcements from appearing in unrelated conversation turns. Items queued longer than 5 minutes are silently dropped with logging. **Changes:** - Introduces `ANNOUNCE_MAX_AGE_MS` constant (5 minutes) - Filters stale items in `scheduleAnnounceDrain` after debounce, before delivery - Adds test confirming stale items are dropped while fresh items deliver normally - Uses existing `enqueuedAt` timestamp field (no type changes needed) **Implementation details:** - TTL check runs in a `while` loop after `waitForQueueDebounce` - Drops items from the head of the queue using `queue.items.shift()` - Breaks out early if queue is empty after filtering - Works for both `followup` and `collect` modes <h3>Confidence Score: 5/5</h3> - Safe to merge - straightforward TTL implementation with proper test coverage - The implementation is clean, well-tested, and follows existing patterns. The TTL logic is correctly placed in the drain flow, handles both queue modes, and includes proper logging. No logical errors, security issues, or edge cases identified. - No files require special attention <sub>Last reviewed commit: 49cd8dc</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs