#19362: feat(discord): clean up sessions when channel is deleted
channel: discord
size: M
Cluster:
Session Management Enhancements
## 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
#15744: fix: allow cross-agent session path validation
by scottgl9 · 2026-02-13
78.0%
#20078: feat(session): Add channelGroups config(optional config) for shared...
by demarlik01 · 2026-02-18
77.5%
#20431: fix(sessions): add session contamination guards and self-leak lock ...
by marcomarandiz · 2026-02-18
75.9%
#16061: fix(sessions): tolerate invalid sessionFile metadata
by haoyifan · 2026-02-14
74.6%
#15050: fix: transcript corruption resilience — strip aborted tool_use bloc...
by yashchitneni · 2026-02-12
74.6%
#17743: fix(agents): disable orphaned user message deletion that causes ses...
by clawrl3000 · 2026-02-16
74.5%
#4664: fix: per-session metadata files to eliminate lock contention
by tsukhani · 2026-01-30
74.2%
#16736: fix: stagger multi-account channel startup to avoid Discord rate li...
by rm289 · 2026-02-15
74.1%
#23158: discord: harden preflight/reply path against slow lookup latency
by danielstarman · 2026-02-22
74.0%
#21463: fix(discord): prevent WebSocket death spiral + fix numeric channel ID…
by akropp · 2026-02-20
73.7%