Skip to content

feat: configurable session recovery#1689

Merged
zapteryx merged 8 commits intonextfrom
feat/configurable-session-recovery
Mar 5, 2026
Merged

feat: configurable session recovery#1689
zapteryx merged 8 commits intonextfrom
feat/configurable-session-recovery

Conversation

@zapteryx
Copy link
Copy Markdown
Member

@zapteryx zapteryx commented Mar 5, 2026

Thank you for your interest in contributing!

Before proceeding, please review the guidelines for contributing.

  • Are you targeting the next branch? (left side)
  • Did you review the guidelines for contributing?
  • Does your code pass linting checks?
  • Is your code documented, if applicable? (Check if not applicable)
  • Does this PR have a linked issue?

Scope of change

  • Major change
  • Minor change
  • Documentation only

Type of change

  • Bug fix
  • Feature
  • Other

Priority

  • Critical
  • High
  • Medium
  • Low

Description

Please describe the changes.
Adds the ability to configure session recovery - disabling, setting max age, and setting the maximum number of attempts

Summary by CodeRabbit

  • New Features

    • Added session recovery controls to let the app optionally restore player sessions after restarts (enable/disable, max age, max attempts).
    • App now only attempts to restore sessions when session recovery is enabled; restoration is subject to time and attempt limits.
  • Documentation

    • Updated configuration docs and example settings to include session recovery options.
    • Added a user-facing message shown when session recovery is disabled.

@zapteryx zapteryx added this to the Quaver 8 milestone Mar 5, 2026
@zapteryx zapteryx requested review from a team as code owners March 5, 2026 09:05
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 5, 2026

Walkthrough

Adds session recovery: new config schema and example, types and settings entry, a PlayerStateManager to read/save/delete states.json with savedAt/attempts, conditional persistence in UpdateHandler, readiness logic to validate/restore snapshots, and a localization key for disabled recovery messaging.

Changes

Cohort / File(s) Summary
Configuration / Examples
CONFIGURATION.md, settings.example.json
Added sessionRecovery config (enabled, maxAge, maxAttempts) to docs and example settings.
Localization
locales/en/music.js
Added SESSION_RECOVERY_DISABLED translation under RESTARTING.
Settings & Types
src/lib/util/settings.ts, src/lib/music/types.ts
Added SessionRecoverySettingsObject and sessionRecovery on SettingsObject; added PlayerStatesRecord type with savedAt and attempts.
Persistence Manager
src/lib/music/PlayerStateManager.ts, src/lib/music/index.ts
New PlayerStateManager class with read, save, delete, and shouldRestore; exported from music index.
Update Persistence
src/lib/UpdateHandler.ts
Persistence of states.json made conditional on sessionRecovery.enabled; when enabled set savedAt and write states.
Ready / Restore Logic
src/events/music/ready.ts
Replaced ad-hoc state reads with PlayerStateManager; added restorePlayer flow, validation against maxAge/maxAttempts, attempt counting, and cleanup/delete of persisted state.
Event Messaging
src/events/music/queueFinish.ts, src/events/music/trackStart.ts
Choose restart/recovery message key based on settings.sessionRecovery.enabled (SESSION_RECOVERY_EXPLANATION vs SESSION_RECOVERY_DISABLED).
Minor docs
src/lib/music/QuaverPlayer.ts
Small JSDoc wording change in setStay method.

Sequence Diagram(s)

sequenceDiagram
    participant UpdateHandler as UpdateHandler
    participant Settings as Settings
    participant FS as FileSystem
    participant ReadyHandler as ReadyEvent
    participant PlayerManager as PlayerStateManager

    rect rgba(200,200,255,0.5)
    UpdateHandler->>Settings: read sessionRecovery.enabled
    alt enabled
        UpdateHandler->>PlayerManager: collect current player states
        PlayerManager-->>UpdateHandler: player states
        UpdateHandler->>FS: write states.json (include savedAt)
    else disabled
        UpdateHandler-->>FS: skip writing states.json
    end
    end

    rect rgba(200,255,200,0.5)
    ReadyHandler->>Settings: read sessionRecovery.enabled, maxAge, maxAttempts
    ReadyHandler->>FS: read states.json
    alt states exist and enabled
        ReadyHandler->>PlayerManager: shouldRestore(states)
        alt shouldRestore == true
            ReadyHandler->>PlayerManager: restorePlayer(snapshot)
            PlayerManager-->>ReadyHandler: restored (success/failure)
            ReadyHandler->>FS: delete states.json
        else shouldRestore == false
            ReadyHandler->>FS: delete states.json
        end
    else no states or disabled
        ReadyHandler-->>PlayerManager: skip restore
    end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: configurable session recovery' accurately and concisely summarizes the main change: introducing configurable session recovery with disable, maxAge, and maxAttempts options.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/configurable-session-recovery

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added priority:p1 Issues and PRs: High priority pr:type:feat PRs: Feature labels Mar 5, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/events/music/ready.ts (1)

99-99: ⚠️ Potential issue | 🔴 Critical

Bug: return exits entire function instead of skipping to next guild.

Using return here exits the execute() function entirely when a snapshot lacks voiceChannelId. This skips restoration for all remaining guilds and bypasses the deletePlayerStates() cleanup at line 141, leaving stale state on disk.

🐛 Proposed fix
-                    if (!snapshot.voiceChannelId) return;
+                    if (!snapshot.voiceChannelId) continue;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/events/music/ready.ts` at line 99, In execute(), inside the loop that
iterates guild snapshots (where the code currently does "if
(!snapshot.voiceChannelId) return;"), change the early exit to skip only the
current guild by using continue (or otherwise proceed to the next iteration)
instead of returning from the whole function; this keeps the loop running for
remaining guilds and ensures deletePlayerStates() is still reached and executed
for cleanup. Ensure you update the check referencing snapshot.voiceChannelId in
the same block and verify no other return statements prematurely exit execute()
before deletePlayerStates().
🧹 Nitpick comments (1)
src/events/music/ready.ts (1)

38-40: Type mismatch: parameter type doesn't include metadata fields.

The function is called with an object containing savedAt and attempts metadata (line 88), but the parameter type only declares Record<string, QuaverPlayerJSON>. Consider aligning the type with the actual usage for clarity and type safety.

♻️ Proposed fix
 async function savePlayerStates(
-    states: Record<string, QuaverPlayerJSON>,
+    states: Record<string, QuaverPlayerJSON> & {
+        savedAt?: number;
+        attempts?: number;
+    },
 ): Promise<void> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/events/music/ready.ts` around lines 38 - 40, The parameter type for
savePlayerStates is too narrow—calls pass objects that include metadata fields
savedAt and attempts—so update the function signature to accept the actual shape
(e.g., Record<string, QuaverPlayerJSON & { savedAt: number | string; attempts?:
number }>) or create a named interface (e.g., PlayerStateWithMeta) and use
Record<string, PlayerStateWithMeta>; then update any call sites and related
types to match so TypeScript reflects the presence of savedAt and attempts and
preserves type safety for savePlayerStates and its callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CONFIGURATION.md`:
- Around line 90-93: Update the sessionRecovery defaults so they're consistent
across docs and the example config: change sessionRecovery.enabled to false and
ensure sessionRecovery.maxAge and sessionRecovery.maxAttempts match the values
in settings.example.json; update both occurrences in CONFIGURATION.md (the block
with "sessionRecovery": { "enabled": ..., "maxAge": ..., "maxAttempts": ... }
and the later documented defaults) so all three fields (sessionRecovery.enabled,
sessionRecovery.maxAge, sessionRecovery.maxAttempts) are identical to
settings.example.json.

---

Outside diff comments:
In `@src/events/music/ready.ts`:
- Line 99: In execute(), inside the loop that iterates guild snapshots (where
the code currently does "if (!snapshot.voiceChannelId) return;"), change the
early exit to skip only the current guild by using continue (or otherwise
proceed to the next iteration) instead of returning from the whole function;
this keeps the loop running for remaining guilds and ensures
deletePlayerStates() is still reached and executed for cleanup. Ensure you
update the check referencing snapshot.voiceChannelId in the same block and
verify no other return statements prematurely exit execute() before
deletePlayerStates().

---

Nitpick comments:
In `@src/events/music/ready.ts`:
- Around line 38-40: The parameter type for savePlayerStates is too narrow—calls
pass objects that include metadata fields savedAt and attempts—so update the
function signature to accept the actual shape (e.g., Record<string,
QuaverPlayerJSON & { savedAt: number | string; attempts?: number }>) or create a
named interface (e.g., PlayerStateWithMeta) and use Record<string,
PlayerStateWithMeta>; then update any call sites and related types to match so
TypeScript reflects the presence of savedAt and attempts and preserves type
safety for savePlayerStates and its callers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e9f83a6a-5a22-47c9-bb6f-0dde52c54177

📥 Commits

Reviewing files that changed from the base of the PR and between c70b076 and c06831b.

📒 Files selected for processing (9)
  • CONFIGURATION.md
  • locales/en/music.js
  • settings.example.json
  • src/events/music/queueFinish.ts
  • src/events/music/ready.ts
  • src/events/music/trackStart.ts
  • src/lib/UpdateHandler.ts
  • src/lib/music/QuaverPlayer.ts
  • src/lib/util/settings.ts

@github-actions github-actions bot added the affects:documentation Issues and PRs: Affects documentation label Mar 5, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/events/music/ready.ts (1)

123-141: ⚠️ Potential issue | 🟠 Major

Don’t clear saved state after partial restore failures.

Line 141 unconditionally deletes states.json, even when restore fails in the catch block at Line 123. That makes configured retry attempts largely ineffective for transient failures.

💡 Preserve state when any restore fails
+        let hadRestoreFailure = false;
         for await (const [
             guildId,
             guildData,
         ] of data.guild.instance.iterator()) {
@@
                 } catch (error) {
+                    hadRestoreFailure = true;
                     logger.error(
                         `[G ${guildId}] Failed to restore player`,
                         error,
                     );
                 }
                 continue;
             }
@@
-        await deletePlayerStates();
+        if (!hadRestoreFailure) {
+            await deletePlayerStates();
+        } else {
+            await savePlayerStates(states);
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/events/music/ready.ts` around lines 123 - 141, The code unconditionally
calls deletePlayerStates() after attempting per-guild restores, which removes
saved state even if any restore caught an error; change this by tracking whether
any restore failed (e.g., a boolean like hadRestoreError) during the loop that
contains the try/catch around player restore, set it true in the catch that logs
the error, and only call deletePlayerStates() if hadRestoreError is false (or
alternatively delete only the successfully restored guild entries instead of the
whole file); refer to the restore loop/catch around logger.error(`[G ${guildId}]
Failed to restore player`, error) and the deletePlayerStates() invocation to
make the conditional deletion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/events/music/ready.ts`:
- Around line 41-44: The code currently calls deletePlayerStates() before
writeFile('states.json', ...), which can permanently lose state if the write
fails; instead, stop deleting upfront and perform an atomic replace: serialize
states to a temporary file (e.g., 'states.json.tmp') using writeFile, fsync if
available, then rename the temp into 'states.json' (or call fs.rename) only
after a successful write; remove the deletePlayerStates() call from the hot path
and, if you still need cleanup, run it only after successful replace or in a
safe recovery/rollback path, and ensure errors from write/rename are caught and
logged so the original file remains intact.
- Around line 64-86: The ready handler's early returns inside execute() stop the
rest of initialization (including the stay-channel bootstrap loop) when session
recovery is disabled or invalid; remove those returns and instead treat session
recovery as optional: after checking settings.sessionRecovery?.enabled, expired
states, or max attempts, log and call deletePlayerStates() as needed but do not
return from execute(); simply skip the restoration flow (e.g., by guarding the
restore logic with a boolean like shouldRestore) so the subsequent
stay-channel/bootstrap loop still runs. Ensure you update the code paths around
readPlayerStates, deletePlayerStates, and the restore section in execute() to
use the guard rather than returning.

---

Outside diff comments:
In `@src/events/music/ready.ts`:
- Around line 123-141: The code unconditionally calls deletePlayerStates() after
attempting per-guild restores, which removes saved state even if any restore
caught an error; change this by tracking whether any restore failed (e.g., a
boolean like hadRestoreError) during the loop that contains the try/catch around
player restore, set it true in the catch that logs the error, and only call
deletePlayerStates() if hadRestoreError is false (or alternatively delete only
the successfully restored guild entries instead of the whole file); refer to the
restore loop/catch around logger.error(`[G ${guildId}] Failed to restore
player`, error) and the deletePlayerStates() invocation to make the conditional
deletion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 89fa1334-66cc-4ddd-88fe-b51dde803697

📥 Commits

Reviewing files that changed from the base of the PR and between c06831b and c4775a1.

📒 Files selected for processing (1)
  • src/events/music/ready.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/events/music/ready.ts (2)

13-18: ⚠️ Potential issue | 🟠 Major

Do not skip stay bootstrap when restore fails for a guild.

If snapshot restore is invalid or throws, the current continue still bypasses the stay path for that guild.

💡 Make restore outcome explicit
 async function restorePlayer(
     guild: QuaverGuild<Initialized> & Guild,
     snapshot: QuaverPlayerJSON,
-): Promise<void> {
+): Promise<boolean> {
     try {
-        if (!snapshot.voiceChannelId) return;
+        if (!snapshot.voiceChannelId) return false;
         const player = await guild.client.music.players.createFromJSON(
             guild,
             snapshot,
         );
@@
         if (snapshot.position > 0) {
             await player.seekTo(snapshot.position);
         }
         logger.info(`[G ${guild.id}] Player restored from saved state`);
+        return true;
     } catch (error) {
         logger.error(`[G ${guild.id}] Failed to restore player`, error);
+        return false;
     }
 }
@@
-            if (snapshot) {
-                await restorePlayer(guild, snapshot);
+            if (snapshot && (await restorePlayer(guild, snapshot))) {
                 continue;
             }

Also applies to: 36-38, 76-79

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/events/music/ready.ts` around lines 13 - 18, The restorePlayer function
currently uses early returns/continues (e.g., when checking
snapshot.voiceChannelId or when restore throws) that skip the "stay bootstrap"
path for that guild; modify restorePlayer so that only the explicit no-voice
case bypasses restoration, but any restoration failure or thrown error does not
skip stay bootstrap—catch exceptions from the restore logic and after
handling/logging the error explicitly invoke or allow the existing
stay-bootstrap code to run for that guild (or set a flag to run it after the
try/catch) so that restore failures still fall back to the stay behavior;
reference restorePlayer and snapshot.voiceChannelId to locate the checks and the
places where the continue/return currently prevents the stay bootstrap from
executing.

58-58: ⚠️ Potential issue | 🟠 Major

Use strict boolean check for recovery enablement.

Given settings are loaded from raw JSON, a truthy check can accidentally enable recovery for non-boolean values.

💡 Minimal fix
-        if (settings.sessionRecovery?.enabled) {
+        if (settings.sessionRecovery?.enabled === true) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/events/music/ready.ts` at line 58, The current truthy check "if
(settings.sessionRecovery?.enabled)" can misinterpret non-boolean JSON values as
enabled; update the condition to an explicit boolean check (e.g.,
settings.sessionRecovery?.enabled === true) wherever session recovery is
evaluated (notably the if in ready.ts referencing
settings.sessionRecovery?.enabled) so only the literal boolean true enables
recovery.
src/lib/music/PlayerStateManager.ts (1)

21-25: ⚠️ Potential issue | 🟠 Major

Avoid delete-before-write in snapshot persistence.

Line 22 deletes the existing state file before writing the next one. If write fails, recovery data is lost.

💡 Safer persistence approach
-import { readFile, unlink, writeFile } from 'node:fs/promises';
+import { readFile, rename, unlink, writeFile } from 'node:fs/promises';

 async save(states: PlayerStatesRecord): Promise<void> {
-    await this.delete();
+    const tmpPath = `${this.filePath}.tmp`;
     try {
-        await writeFile(this.filePath, JSON.stringify(states, null, 4));
+        await writeFile(tmpPath, JSON.stringify(states, null, 4));
+        await rename(tmpPath, this.filePath);
     } catch (error) {
         logger.error(error);
+        try {
+            await unlink(tmpPath);
+        } catch {}
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/music/PlayerStateManager.ts` around lines 21 - 25, The save method in
PlayerStateManager currently calls delete() before writeFile, risking data loss
if write fails; remove the pre-write delete and implement atomic persistence:
write the JSON to a temporary file (e.g., this.filePath + '.tmp') using
writeFile, fs.promises.writeFile or equivalent, then atomically rename/move the
temp file to this.filePath using fs.promises.rename; ensure errors during write
do not remove the existing file and clean up the temp file on failure, and
update references to writeFile, delete, save, and filePath accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/music/PlayerStateManager.ts`:
- Around line 42-44: The current guard uses truthy checks that incorrectly treat
0 as falsy; change them to explicit numeric checks: replace the `states.savedAt
&& Date.now() - states.savedAt > (settings.sessionRecovery.maxAge ?? 86400) *
1000` style guard with an explicit numeric check such as `typeof states.savedAt
=== "number" && !Number.isNaN(states.savedAt) && Date.now() - states.savedAt >
(settings.sessionRecovery.maxAge ?? 86400) * 1000`, and similarly replace any
`states.attempts` truthy checks with `typeof states.attempts === "number" &&
!Number.isNaN(states.attempts) && states.attempts > 0` (or the appropriate
threshold) so zero is handled correctly; update the conditions in
PlayerStateManager where `states.savedAt` and `states.attempts` are used (lines
shown in the diff).

---

Duplicate comments:
In `@src/events/music/ready.ts`:
- Around line 13-18: The restorePlayer function currently uses early
returns/continues (e.g., when checking snapshot.voiceChannelId or when restore
throws) that skip the "stay bootstrap" path for that guild; modify restorePlayer
so that only the explicit no-voice case bypasses restoration, but any
restoration failure or thrown error does not skip stay bootstrap—catch
exceptions from the restore logic and after handling/logging the error
explicitly invoke or allow the existing stay-bootstrap code to run for that
guild (or set a flag to run it after the try/catch) so that restore failures
still fall back to the stay behavior; reference restorePlayer and
snapshot.voiceChannelId to locate the checks and the places where the
continue/return currently prevents the stay bootstrap from executing.
- Line 58: The current truthy check "if (settings.sessionRecovery?.enabled)" can
misinterpret non-boolean JSON values as enabled; update the condition to an
explicit boolean check (e.g., settings.sessionRecovery?.enabled === true)
wherever session recovery is evaluated (notably the if in ready.ts referencing
settings.sessionRecovery?.enabled) so only the literal boolean true enables
recovery.

In `@src/lib/music/PlayerStateManager.ts`:
- Around line 21-25: The save method in PlayerStateManager currently calls
delete() before writeFile, risking data loss if write fails; remove the
pre-write delete and implement atomic persistence: write the JSON to a temporary
file (e.g., this.filePath + '.tmp') using writeFile, fs.promises.writeFile or
equivalent, then atomically rename/move the temp file to this.filePath using
fs.promises.rename; ensure errors during write do not remove the existing file
and clean up the temp file on failure, and update references to writeFile,
delete, save, and filePath accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 39d4c534-ccab-408c-9256-aa049c4bc02e

📥 Commits

Reviewing files that changed from the base of the PR and between c4775a1 and dc95559.

📒 Files selected for processing (5)
  • src/events/music/queueFinish.ts
  • src/events/music/ready.ts
  • src/lib/music/PlayerStateManager.ts
  • src/lib/music/index.ts
  • src/lib/music/types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/events/music/queueFinish.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/events/music/ready.ts (1)

79-92: ⚠️ Potential issue | 🟠 Major

Do not delete recovery state when any guild restore fails.

Line 92 deletes all saved snapshots unconditionally. If restoration fails for a guild at Line 79, that snapshot is still erased, so next startup cannot retry recovery (making maxAttempts ineffective for partial failures).

💡 Suggested fix (preserve failed snapshots, delete only on full success)
+        const failedStates: PlayerStatesRecord = {};
+        let hasRestoreFailure = false;
         for await (const [
             guildId,
             guildData,
         ] of data.guild.instance.iterator()) {
@@
             const snapshot = states[guildId];
             if (snapshot) {
                 const restored = await restorePlayer(guild, snapshot);
-                if (restored) continue;
+                if (restored) continue;
+                failedStates[guildId] = snapshot;
+                hasRestoreFailure = true;
             }
@@
         }
-        await stateManager.delete();
+        if (!settings.sessionRecovery?.enabled) return;
+        if (hasRestoreFailure) {
+            failedStates.savedAt = states.savedAt;
+            failedStates.attempts = states.attempts;
+            await stateManager.save(failedStates);
+        } else {
+            await stateManager.delete();
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/events/music/ready.ts` around lines 79 - 92, The code currently calls
stateManager.delete() unconditionally which erases all snapshots even if some
guild restores failed; update the logic in the ready handler to only remove
snapshots that were successfully restored (or call stateManager.delete() only
when all restorePlayer(guild, snapshot) calls succeeded). Concretely, use
restorePlayer's return value to track per-guild success (e.g., collect
failedGuilds or a success flag) and either (a) call stateManager.delete() only
when every restorePlayer returned true, or (b) delete individual snapshots for
guilds that returned true while preserving those that failed so maxAttempts can
retry later; adjust any loop around restorePlayer, snapshot and
stateManager.delete accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/events/music/ready.ts`:
- Around line 79-92: The code currently calls stateManager.delete()
unconditionally which erases all snapshots even if some guild restores failed;
update the logic in the ready handler to only remove snapshots that were
successfully restored (or call stateManager.delete() only when all
restorePlayer(guild, snapshot) calls succeeded). Concretely, use restorePlayer's
return value to track per-guild success (e.g., collect failedGuilds or a success
flag) and either (a) call stateManager.delete() only when every restorePlayer
returned true, or (b) delete individual snapshots for guilds that returned true
while preserving those that failed so maxAttempts can retry later; adjust any
loop around restorePlayer, snapshot and stateManager.delete accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cc4b8d59-d3a6-426b-80b4-9b7b3fdbe903

📥 Commits

Reviewing files that changed from the base of the PR and between dc95559 and ed6f2a8.

📒 Files selected for processing (1)
  • src/events/music/ready.ts

@zapteryx zapteryx added status:confirmed Issues and PRs: Confirmed bug, or enhancement is confirmed to be added status:tested PRs: Tested and working with no issues labels Mar 5, 2026
@zapteryx zapteryx merged commit 4f796dd into next Mar 5, 2026
17 of 18 checks passed
@zapteryx zapteryx deleted the feat/configurable-session-recovery branch March 5, 2026 10:28
zptx-bot added a commit that referenced this pull request Mar 5, 2026
# [8.0.0-next.34](8.0.0-next.33...8.0.0-next.34) (2026-03-05)

### Features

* configurable session recovery ([#1689](#1689)) ([4f796dd](4f796dd))
@zptx-bot
Copy link
Copy Markdown
Member

zptx-bot commented Mar 5, 2026

🎉 This PR is included in version 8.0.0-next.34 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

affects:documentation Issues and PRs: Affects documentation pr:type:feat PRs: Feature priority:p1 Issues and PRs: High priority released on @next status:confirmed Issues and PRs: Confirmed bug, or enhancement is confirmed to be added status:tested PRs: Tested and working with no issues

Development

Successfully merging this pull request may close these issues.

Stop restoring player state if state is older than a day (or make it configurable)

2 participants