fix(discord): escalate repeated health-monitor restarts#72309
Conversation
🔒 Aisle Security AnalysisWe found 1 potential security issue(s) in this PR:
1. 🟡 Restart throttling and manual-stop bypass via eviction of per-account lifecycle state on whole-channel reload
DescriptionDuring a whole-channel reload ( This eviction clears two safety controls:
If
Vulnerable code: store.runtimes.delete(id);
restartAttempts.delete(restartKey(channelId, id));
manuallyStopped.delete(restartKey(channelId, id));RecommendationAvoid clearing throttling/manual-stop state purely based on a transient Options:
Example (preserve safety state): // only evict runtime snapshot; preserve safety controls
store.runtimes.delete(id);
// DO NOT delete restartAttempts/manuallyStopped hereOr with TTL: const rKey = restartKey(channelId, id);
evictedAt.set(rKey, Date.now());
// clear manuallyStopped/restartAttempts only if evictedAt is older than TTLAnalyzed PR: #72309 at commit Last updated on: 2026-04-26T18:01:05Z |
Greptile SummaryThis PR fixes Discord gateway recovery looping (#38596) via two targeted changes: moving health-monitor restart accounting before the restart attempt so that failed restarts count toward cooldown and hourly caps, and evicting stale account lifecycle state during full channel reloads so old status cannot re-trigger the recovery loop. Both changes are covered by new dedicated tests. Confidence Score: 4/5Safe to merge; logic is correct and well-tested with one minor P2 cleanup gap in eviction coverage. No P0 or P1 issues. A single P2 note about orphan restartAttempts/manuallyStopped keys not being covered by the eviction loop when a runtime entry no longer exists; in practice this is unlikely to cause misbehavior but represents incomplete state cleanup. src/gateway/server-channels.ts — evictStaleChannelAccountState eviction loop Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/gateway/server-channels.ts
Line: 291-303
Comment:
**Stale `restartAttempts`/`manuallyStopped` keys not cleaned up when no runtime entry exists**
`evictStaleChannelAccountState` iterates only `store.runtimes.keys()`, so any account that has an entry in `restartAttempts` or `manuallyStopped` but whose runtime was already cleaned up by a previous stop would retain stale state even after a channel reload. In practice this can prevent the "reset to clean state" goal of the eviction: on the next full reload the hourly health-monitor caps from the old account will silently survive. Consider also iterating the keyset of `restartAttempts`/`manuallyStopped` scoped to this `channelId` (or clearing them unconditionally for removed accounts).
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(discord): escalate repeated health-m..." | Re-trigger Greptile |
| for (const id of store.runtimes.keys()) { | ||
| if ( | ||
| activeAccountIds.has(id) || | ||
| store.aborts.has(id) || | ||
| store.starting.has(id) || | ||
| store.tasks.has(id) | ||
| ) { | ||
| continue; | ||
| } | ||
| store.runtimes.delete(id); | ||
| restartAttempts.delete(restartKey(channelId, id)); | ||
| manuallyStopped.delete(restartKey(channelId, id)); | ||
| } |
There was a problem hiding this comment.
Stale
restartAttempts/manuallyStopped keys not cleaned up when no runtime entry exists
evictStaleChannelAccountState iterates only store.runtimes.keys(), so any account that has an entry in restartAttempts or manuallyStopped but whose runtime was already cleaned up by a previous stop would retain stale state even after a channel reload. In practice this can prevent the "reset to clean state" goal of the eviction: on the next full reload the hourly health-monitor caps from the old account will silently survive. Consider also iterating the keyset of restartAttempts/manuallyStopped scoped to this channelId (or clearing them unconditionally for removed accounts).
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/server-channels.ts
Line: 291-303
Comment:
**Stale `restartAttempts`/`manuallyStopped` keys not cleaned up when no runtime entry exists**
`evictStaleChannelAccountState` iterates only `store.runtimes.keys()`, so any account that has an entry in `restartAttempts` or `manuallyStopped` but whose runtime was already cleaned up by a previous stop would retain stale state even after a channel reload. In practice this can prevent the "reset to clean state" goal of the eviction: on the next full reload the hourly health-monitor caps from the old account will silently survive. Consider also iterating the keyset of `restartAttempts`/`manuallyStopped` scoped to this `channelId` (or clearing them unconditionally for removed accounts).
How can I resolve this? If you propose a fix, please make it concise.
Summary
Repairs and lands the narrow Discord health-monitor restart escalation path from #40413 for #38596.
Credit
Based on contributor work in #40413 by @jellyAI-dev, with existing follow-up commits credited in that PR. If reconnect-grace behavior from #45712 is incorporated, credit @cass-clearly as well.
Plan
pnpm check:changed.Fixes #38596. Related: #45712, #71546.
ProjectClownfish replacement details: