Conversation
…cle cleanup) 🔴 CRITICAL: Cancel TurnEndIdleCts on both reconnect paths - SendPromptAsync reconnect path: add CancelTurnEndFallback(state) alongside CancelProcessingWatchdog(state) so the old 4s fallback timer cannot fire CompleteResponse on the new shared Info object after reconnect - TryRecoverPermissionAsync: same fix — cancel fallback before creating newState 🟡 MODERATE: Extract CancelTurnEndFallback helper (mirrors CancelProcessingWatchdog) - Cancel + Dispose to prevent kernel timer resource leak over many cycles - Replace all 8 inline Interlocked.Exchange + Cancel patterns with helper 🟡 MODERATE: Add CancelTurnEndFallback to lifecycle methods - DisposeAsync: cancel alongside CancelProcessingWatchdog in session cleanup loop - CloseSessionCoreAsync: cancel before SDK session dispose - InitializeAsync cleanup: cancel in re-init teardown loop 🟢 MINOR: Extract TurnEndIdleFallbackMs constant (was magic 4000) 🟢 MINOR: Volatile.Read → Interlocked.CompareExchange for SuccessfulToolCountThisTurn read in TryRecoverPermissionAsync (consistency with other Interlocked sites) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… guard, token capture) Moderate: SessionErrorEvent missing CancelTurnEndFallback (INV-1 gap) - Add CancelTurnEndFallback(state) in SessionErrorEvent handler alongside CancelProcessingWatchdog, closing the last path where a 4s fallback timer could fire CompleteResponse on an already-errored session. Moderate: 4s fallback fires during long tool execution - Add ActiveToolCallCount guard inside the fallback Task.Run closure: if tools are still active after the delay, skip CompleteResponse (AssistantTurnStartEvent is coming to cancel it; this is just defense-in-depth). Minor: Token capture after CTS publish (race hardening) - Capture fallbackToken = idleFallbackCts.Token BEFORE publishing the CTS via Interlocked.Exchange to state.TurnEndIdleCts. Prevents a theoretical race where CancelTurnEndFallback disposes the CTS before .Token is read. Minor: Revert Interlocked.CompareExchange(x,0,0) back to Volatile.Read - Volatile.Read is the idiomatic pattern for a pure read of an int field alongside Interlocked writes; CompareExchange(x,0,0) looks like a reset. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Review Findings (3rd pass -- 5-model consensus)
|
review) Address PR #305 review comment issuecomment-4019192428: 1. Make TurnEndIdleFallbackMs internal for test accessibility 2. Add TurnEndIdleFallbackTests (5 tests): - CancelTurnEndFallback pattern cancels and disposes CTS - Null field does not throw - Fallback does NOT fire when cancelled by SessionIdle within delay - Fallback DOES fire when no SessionIdle arrives - Constant value is 4000ms 3. Add TurnEndFallbackTests (6 tests): - CTS cancel pattern cancels token - Null CTS is safe - Timer fires when not cancelled - Timer does not fire when cancelled before delay - Explicit IsCancellationRequested guard prevents late firing - Constant is in reasonable range (1-30s) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🤖 Multi-Model Consensus Review (Re-Review #3)CI Status:
|
…allback Address PR #305 review comment issuecomment-4019363353 (suggested tests 3 & 4): 3. Fallback_DoesNotFire_WhenToolsWereUsedThisTurn: verifies HasUsedToolsThisTurn guard blocks CompleteResponse when tools were used this turn (new TurnStart is expected from LLM reasoning) 3b. Fallback_DoesFire_WhenNoToolsUsedAndNoSessionIdle: positive case — no tools used and no SessionIdle → CompleteResponse fires correctly 4. MultipleRapidTurnEnds_NoCtsLeak: 10 rapid TurnEnd events — verifies all replaced CTS instances are cancelled+disposed (Interlocked.Exchange pattern), only the latest CTS remains active All 31 TurnEnd/fallback/permission tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…298) When auto-recovery from permission denial succeeds, skip auto-resend if any tools already completed successfully this turn. This avoids re-executing side-effectful tools that already finished before the denial. - Add SuccessfulToolCountThisTurn to SessionState, increment on successful tool completion - Gate TryRecoverPermissionAsync resend: skip if SuccessfulToolCountThisTurn > 0 - Show prominent '⟳ Resending: ...' message when resending - Show 'Auto-resend skipped' message when resend is skipped - Reset/transfer alongside HasUsedToolsThisTurn in all cleanup paths Fixes: #298 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…onAsync cancellation, restored comment - Changed TurnEndIdleCts from property to field to fix 7 CS0206 compile errors with Interlocked.Exchange(ref ...) - Added TurnEndIdleCts cancellation in AbortSessionAsync to prevent delayed CompleteResponse on aborted sessions - Restored deleted // Release send lock comment Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cle cleanup) 🔴 CRITICAL: Cancel TurnEndIdleCts on both reconnect paths - SendPromptAsync reconnect path: add CancelTurnEndFallback(state) alongside CancelProcessingWatchdog(state) so the old 4s fallback timer cannot fire CompleteResponse on the new shared Info object after reconnect - TryRecoverPermissionAsync: same fix — cancel fallback before creating newState 🟡 MODERATE: Extract CancelTurnEndFallback helper (mirrors CancelProcessingWatchdog) - Cancel + Dispose to prevent kernel timer resource leak over many cycles - Replace all 8 inline Interlocked.Exchange + Cancel patterns with helper 🟡 MODERATE: Add CancelTurnEndFallback to lifecycle methods - DisposeAsync: cancel alongside CancelProcessingWatchdog in session cleanup loop - CloseSessionCoreAsync: cancel before SDK session dispose - InitializeAsync cleanup: cancel in re-init teardown loop 🟢 MINOR: Extract TurnEndIdleFallbackMs constant (was magic 4000) 🟢 MINOR: Volatile.Read → Interlocked.CompareExchange for SuccessfulToolCountThisTurn read in TryRecoverPermissionAsync (consistency with other Interlocked sites) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… guard, token capture) Moderate: SessionErrorEvent missing CancelTurnEndFallback (INV-1 gap) - Add CancelTurnEndFallback(state) in SessionErrorEvent handler alongside CancelProcessingWatchdog, closing the last path where a 4s fallback timer could fire CompleteResponse on an already-errored session. Moderate: 4s fallback fires during long tool execution - Add ActiveToolCallCount guard inside the fallback Task.Run closure: if tools are still active after the delay, skip CompleteResponse (AssistantTurnStartEvent is coming to cancel it; this is just defense-in-depth). Minor: Token capture after CTS publish (race hardening) - Capture fallbackToken = idleFallbackCts.Token BEFORE publishing the CTS via Interlocked.Exchange to state.TurnEndIdleCts. Prevents a theoretical race where CancelTurnEndFallback disposes the CTS before .Token is read. Minor: Revert Interlocked.CompareExchange(x,0,0) back to Volatile.Read - Volatile.Read is the idiomatic pattern for a pure read of an int field alongside Interlocked writes; CompareExchange(x,0,0) looks like a reset. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nDenialText tests - Add HasUsedToolsThisTurn guard in TurnEnd fallback: skip CompleteResponse if tools were used this turn, covering the 'fast tools + delayed TurnStart >4s' race window (LLM reasoning between sub-turns) - Add explicit fallbackToken.IsCancellationRequested check before guards - Add catch (Exception ex) in fallback Task.Run to prevent swallowed errors - Make IsPermissionDenialText internal static for testability - Add PermissionDenialDetectionTests (17 tests) covering null/empty, permission denied variants, SDK error codes, and unrelated text Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
review) Address PR #305 review comment issuecomment-4019192428: 1. Make TurnEndIdleFallbackMs internal for test accessibility 2. Add TurnEndIdleFallbackTests (5 tests): - CancelTurnEndFallback pattern cancels and disposes CTS - Null field does not throw - Fallback does NOT fire when cancelled by SessionIdle within delay - Fallback DOES fire when no SessionIdle arrives - Constant value is 4000ms 3. Add TurnEndFallbackTests (6 tests): - CTS cancel pattern cancels token - Null CTS is safe - Timer fires when not cancelled - Timer does not fire when cancelled before delay - Explicit IsCancellationRequested guard prevents late firing - Constant is in reasonable range (1-30s) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…allback Address PR #305 review comment issuecomment-4019363353 (suggested tests 3 & 4): 3. Fallback_DoesNotFire_WhenToolsWereUsedThisTurn: verifies HasUsedToolsThisTurn guard blocks CompleteResponse when tools were used this turn (new TurnStart is expected from LLM reasoning) 3b. Fallback_DoesFire_WhenNoToolsUsedAndNoSessionIdle: positive case — no tools used and no SessionIdle → CompleteResponse fires correctly 4. MultipleRapidTurnEnds_NoCtsLeak: 10 rapid TurnEnd events — verifies all replaced CTS instances are cancelled+disposed (Interlocked.Exchange pattern), only the latest CTS remains active All 31 TurnEnd/fallback/permission tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
8029c82 to
45cb6f0
Compare
…ack, improved permission detection (PureWeen#305) ## Fixes issues from PR PureWeen#285 ### Changes - **Gate auto-resend on successful tools** (fixes PureWeen#298): Added `SuccessfulToolCountThisTurn` to SessionState. Permission recovery skips resend when tools already completed this turn, preventing re-execution of side-effectful work. Shows `⟳ Resending: "..."` when resending, or `Auto-resend skipped` when skipped. - **TurnEnd→Idle fallback** (addresses PureWeen#299): When AssistantTurnEndEvent fires, schedule a 4s delayed CompleteResponse. Cancelled when SessionIdleEvent arrives (normal path) or a new turn starts. - **Improved permission denial detection** (improves PureWeen#300): Extracted IsPermissionDenialText() helper matching "Permission denied", "denied-no-approval-rule", and "could not request permission". ### Issues Addressed - Fixes PureWeen#298 - Addresses PureWeen#299 (workaround for SDK bug) - Improves PureWeen#300 (more robust detection) ### Verification - Build: 0 errors - 20/20 tests pass --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes issues from PR #285
Changes
SuccessfulToolCountThisTurnto SessionState. Permission recovery skips resend when tools already completed this turn, preventing re-execution of side-effectful work. Shows⟳ Resending: "..."when resending, orAuto-resend skippedwhen skipped.Issues Addressed
Verification