Skip to content

Commit 5e565fd

Browse files
PureWeenCopilot
andauthored
fix: preserve stale shell fingerprint across CompleteResponse (#574)
## What One-line fix: `ClearProcessingState` now uses `preserveCarryOver: true` when clearing deferred idle tracking, matching what `SendPromptAsync` already does. ## Why When the CLI leaks stale shell entries in `backgroundTasks` across turns (issue #573), the carryover detection (`ShouldIgnoreCarryOverShellOnlyTasks`) should recognize them as old and skip the IDLE-DEFER. But `ClearProcessingState` was wiping the fingerprint and firstSeenTicks on every turn completion, so the next turn saw the same stale shells as brand-new and deferred for ~10 minutes every time. ## The bug path 1. Turn completes → `CompleteResponse` → `ClearProcessingState` → wipes `DeferredBackgroundTaskFingerprint` and `FirstSeenTicks` 2. User sends new message → `SendPromptAsync` calls `ClearDeferredIdleTracking(preserveCarryOver: true)` — but the data is already gone 3. `session.idle` arrives with `shells=2` (stale from CLI) 4. `RefreshDeferredBackgroundTaskTracking` sees `previousTicks=0`, sets `firstSeenTicks = now` 5. `ShouldIgnoreCarryOverShellOnlyTasks` checks `firstSeenTicks < processingStartedAt` — **false** (both are ~now) 6. IDLE-DEFER kicks in, watchdog eventually force-completes after ~10 min ## The fix `preserveCarryOver: true` in `ClearProcessingState` keeps the shell fingerprint and age across the complete→send boundary, so step 5 correctly detects the shells as carried over and skips the defer. ## Testing - 3323/3323 tests pass - Specifically verified `BackgroundTasksIdleTests`, `ProcessingWatchdogTests`, and `ChatExperienceSafetyTests` (254 targeted tests) --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 66af028 commit 5e565fd

2 files changed

Lines changed: 2 additions & 2 deletions

File tree

PolyPilot.Tests/SessionStabilityTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public void ForceCompleteProcessing_UsesClearProcessingState()
139139
{
140140
// ForceCompleteProcessingAsync must delegate to ClearProcessingState rather than
141141
// manually clearing fields. ClearProcessingState calls ClearDeferredIdleTracking
142-
// internally (without preserveCarryOver, which is only needed in SendPromptAsync).
142+
// with preserveCarryOver: true so stale shell fingerprints survive across turns.
143143
var source = File.ReadAllText(TestPaths.OrganizationCs);
144144
var method = ExtractMethod(source, "Task ForceCompleteProcessingAsync");
145145

PolyPilot/Services/CopilotService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ private void ClearProcessingState(SessionState state, bool accumulateApiTime = t
797797
Interlocked.Exchange(ref state.SendingFlag, 0);
798798
Interlocked.Exchange(ref state.ActiveToolCallCount, 0);
799799
state.HasUsedToolsThisTurn = false;
800-
ClearDeferredIdleTracking(state);
800+
ClearDeferredIdleTracking(state, preserveCarryOver: true);
801801
Interlocked.Exchange(ref state.SuccessfulToolCountThisTurn, 0);
802802
Interlocked.Exchange(ref state.ToolHealthStaleChecks, 0);
803803
Interlocked.Exchange(ref state.EventCountThisTurn, 0);

0 commit comments

Comments
 (0)