← Back to PRs

#19362: feat(discord): clean up sessions when channel is deleted

by chubes4 open 2026-02-17 18:27 View on GitHub →
channel: discord size: M
## Summary Automatically clean up orphaned sessions when a Discord channel is deleted, preventing session store bloat. Closes #19333 ## Problem When a Discord channel is deleted, the associated OpenClaw session persists indefinitely in the session store. Over time — especially during development with frequent test channel creation/deletion — this causes significant session bloat. We found **38 orphaned sessions** across a 3-bot fleet from deleted channels. ## Solution Add a `DiscordChannelDeleteListener` that listens for Discord's `CHANNEL_DELETE` gateway event and removes the associated session(s) from the store. ### Changes - **`src/discord/monitor/listeners.ts`**: New `DiscordChannelDeleteListener` class extending Carbon's `ChannelDeleteListener`. On channel delete, scans all agent session stores for keys matching `discord:channel:<channelId>`, removes entries, and archives transcripts. - **`src/discord/monitor/provider.ts`**: Register the new listener alongside existing message/reaction/presence listeners. - **`src/discord/monitor.ts`**: Export the new listener class and params type. ### Design Decisions - **Multi-agent aware**: Iterates over all agent IDs via `listAgentIds()` since a channel could be bound to any agent in multi-agent setups. - **Lazy imports**: Session store utilities are imported dynamically to avoid circular dependency issues and keep the listener module lightweight. - **No new intents required**: `CHANNEL_DELETE` requires the `Guilds` intent, which is already enabled by default in `gateway-plugin.ts`. - **Best-effort transcript archival**: Uses the existing `archiveSessionTranscripts` function with `reason: "deleted"`, matching the behavior of `sessions.delete`. - **Follows existing patterns**: Mirrors the structure of `DiscordReactionListener` and `DiscordPresenceListener` (constructor params, try/catch, slow listener logging). ## Testing This was discovered and validated on a production fleet where 38 sessions were orphaned from deleted Discord channels. The listener follows the same session store mutation pattern used by the cron session reaper (`src/cron/session-reaper.ts`). <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR adds a `DiscordChannelDeleteListener` that listens for Discord's `CHANNEL_DELETE` gateway event and removes associated sessions from the store, addressing session bloat from orphaned sessions when channels are deleted. The overall design is sound and follows established patterns (`DiscordPresenceListener`, cron session-reaper), and the multi-agent awareness via `listAgentIds()` is appropriate. Key issues found: - **Channel ID extraction fragility** (`listeners.ts:492–501`): The two-path fallback extracts the channel ID, but the `"id" in params.data` check has no guard for an empty string — `String(...)` on an empty or undefined value would produce `""` or `"undefined"`, silently matching no sessions or worse. The first branch is almost certainly always taken for valid events (raw Discord `GatewayChannelDeleteDispatchData` always has `id` at the top level), so the fallback path is dead code in practice, but the missing empty-string guard is still a correctness concern. - **TOCTOU race between initial store read and write lock** (`listeners.ts:519–570`): `matchingKeys` and `sessionsToArchive` are built from a potentially-cached `loadSessionStore` call, but `updateSessionStore` re-reads the store from disk inside the write lock. Sessions written to the store in the narrow window between these two reads will be correctly deleted (because `matchingKeys` is captured and re-used inside the mutator), but those sessions' transcripts won't be archived. The fix is to populate `sessionsToArchive` inside the `updateSessionStore` callback, before deleting. - **Archived transcripts are never cleaned up** (`listeners.ts:558–568`): `archiveSessionTranscripts` returns the list of archived paths, but the return value is discarded. Unlike every other call site in the codebase (`store.ts`), `cleanupArchivedSessionTranscripts` is never called, so archived `.deleted.<timestamp>` transcript files will accumulate indefinitely. <h3>Confidence Score: 3/5</h3> - This PR is mostly safe to merge but has correctness issues around session transcript handling and a minor channel ID extraction fragility. - The core session cleanup logic is correct and follows established patterns. However, the TOCTOU issue means sessions created in a narrow window could have their transcripts silently deleted without archival, and the missing cleanup of archived transcript files introduces a slow storage leak. These don't cause data corruption but represent incomplete implementation of the intended behavior. - src/discord/monitor/listeners.ts — specifically the handleDiscordChannelDelete function: session archival before vs. inside the store lock, and the discarded archiveSessionTranscripts return value. <sub>Last reviewed commit: 81c6bd9</sub> <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment -->

Most Similar PRs