From 37e95e8b09ddd33e8285a3baf7c9ce3977805877 Mon Sep 17 00:00:00 2001 From: AndyMik90 Date: Fri, 20 Feb 2026 12:38:06 +0100 Subject: [PATCH] fix(terminal): capture generation after clearAutoResumeQueue to prevent self-cancel resumeAllPendingClaude captured autoResumeGeneration before calling clearAutoResumeQueue(), which increments the generation counter. This caused the generation mismatch check to always evaluate true, aborting the function before resuming any terminals. Move the capture to after the clear call so the function only aborts on genuinely new external cancellations. Also documents the implicit mutex invariant between the active-terminal effect, processAutoResumeQueue, and resumeAllPendingClaude. Co-Authored-By: Claude Opus 4.6 --- .../src/renderer/stores/terminal-store.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/frontend/src/renderer/stores/terminal-store.ts b/apps/frontend/src/renderer/stores/terminal-store.ts index 77b86b8e..88d275bb 100644 --- a/apps/frontend/src/renderer/stores/terminal-store.ts +++ b/apps/frontend/src/renderer/stores/terminal-store.ts @@ -125,6 +125,12 @@ const AUTO_RESUME_INITIAL_DELAY_MS = 1500; const AUTO_RESUME_STAGGER_MS = 500; // Auto-resume queue state (single-threaded JS assumption - see processAutoResumeQueue) +// +// Mutex invariant: The active-terminal effect (which triggers enqueueAutoResume) and +// processAutoResumeQueue / resumeAllPendingClaude are mutually coordinated through +// clearAutoResumeQueue(). When a project switch or "Resume All" occurs, clearAutoResumeQueue() +// cancels any pending timer, empties the queue, and bumps autoResumeGeneration to abort +// any in-flight processing run. This ensures only one resumption path is active at a time. let autoResumeQueue: string[] = []; let autoResumeTimer: ReturnType | null = null; let autoResumeProcessing = false; @@ -674,14 +680,15 @@ export const useTerminalStore = create((set, get) => ({ } isResumingAll = true; - // Capture generation BEFORE clearAutoResumeQueue() bumps it, so this call - // doesn't immediately invalidate itself, but CAN be cancelled by external calls - const generation = autoResumeGeneration; - try { // Clear auto-resume queue to prevent redundant processing clearAutoResumeQueue(); + // Capture generation AFTER clearAutoResumeQueue() bumps it, so this call + // doesn't immediately self-cancel, but CAN be cancelled by subsequent external + // calls to clearAutoResumeQueue() (e.g., project switch) + const generation = autoResumeGeneration; + const state = get(); // Filter terminals with pending Claude resume