Fix session freezing, add Copilot Console, harden thread safety#153
Merged
Fix session freezing, add Copilot Console, harden thread safety#153
Conversation
Dashboard.HandleComplete used ScheduleRender() which goes through a 50ms debounced timer pipeline. When CompleteResponse fires OnStateChanged followed by OnSessionComplete, the OnStateChanged is throttled (500ms window from recent streaming events), and the ScheduleRender timer can fail to produce a render — leaving cards permanently stuck on 'processing'. Fix: Call StateHasChanged() directly in HandleComplete. This is safe because we're already on the UI thread (called synchronously from CompleteResponse → OnSessionComplete). The Blazor render happens after the current callback completes, by which time IsProcessing is already false. Root cause confirmed via MauiDevFlow CDP inspection: navigating away from Dashboard and back caused a fresh render that correctly showed all sessions as idle, proving IsProcessing was false in the C# model but the Blazor DOM was stale from a missed render. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a new menu item that launches copilot --resume <sessionId> --yolo in Terminal.app, allowing the user to interact with the same session from the CLI. Uses the same shell script + 'open -a Terminal' pattern as the existing Fix with Copilot feature for proper sandbox escape. The existing Terminal action (opens directory) is preserved below it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Extract ShellEscape to PlatformHelper for testability and reuse - Use single-quote escaping to prevent shell injection in Copilot Console - Use InvokeAsync(StateHasChanged) in HandleComplete for defensive thread safety - Add 5 ShellEscape tests covering special chars, empty strings, quotes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Wrap all state mutations inside InvokeAsync to prevent thread-pool threads from racing with UI-thread rendering on non-thread-safe collections (HashSet, Dictionary) - Add _disposed guard to both InvokeAsync lambdas to prevent StateHasChanged on a disposed component after navigation - Found by 4-model fleet review (Opus, GPT-5.3, Sonnet consensus) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen
added a commit
that referenced
this pull request
Feb 21, 2026
…edge Add comprehensive documentation of the recurring stuck-session bug pattern (7 PRs, 16 fix/regression cycles) to copilot-instructions.md: - Full cleanup checklist for all IsProcessing=false paths - Table of all 7 paths with locations - 7 common mistakes with PR references where each occurred - Staleness check and IsResumed clearing documentation - Cross-thread volatile field requirements - ProcessingGeneration guard explanation - Watchdog diagnostic log tag additions This knowledge was hard-won across PRs #141, #147, #148, #153, #158, #163, #164 and should prevent future regressions by making the invariants explicit and discoverable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen
added a commit
that referenced
this pull request
Feb 25, 2026
## Problem After app restart, resumed sessions that were mid-turn show **Thinking...** with a Stop button. The user must manually click Stop every time. The existing watchdog waited 600s (10 min!) before clearing stuck IsProcessing. ## Solution Add a **30s resume quiescence timeout** for sessions that receive zero SDK events after restart. If no events flow within 30s of app start, the session is cleared as stuck. ### Key design decisions (informed by 4-model consultation: Opus 4.6, Sonnet 4.6, Codex 5.3, GPT-5.1): 1. **30s quiescence** — short enough users don't wait, long enough for SDK reconnect (~5s typical, 6x safety margin) 2. **Event-gated** — only fires when \HasReceivedEventsSinceResume == false\. Once events start flowing, transitions to normal 120s/600s timeout tiers 3. **Seed from DateTime.UtcNow, NOT file time** — all 3 models independently flagged that seeding from events.jsonl would cause immediate kills for sessions >15s old (exact PR #148 regression pattern) 4. **Reuses existing watchdog fire path** — no new IsProcessing cleanup code, all 8 invariants preserved ### Timeout tiers (3-tier, was 2-tier): | Condition | Timeout | |-----------|---------| | Resumed, zero events since restart | **30s** (NEW) | | Normal processing, no tools | 120s | | Active tools / resumed with events / multi-agent | 600s | ## Tests - **16 new regression guard tests** covering quiescence edge cases, seed time safety, exhaustive timeout matrix - Updated existing tests to use \ComputeEffectiveTimeout\ helper mirroring production 3-tier formula - **108 total watchdog+recovery tests pass** ✅ ## Regression history context This code has been through 7 PRs of fix/regression cycles (PRs #141→#147→#148→#153→#158→#163→#164). The most relevant precedent: PR #148 added a 10s resume timeout that killed active sessions. Our 30s timeout avoids this by being event-gated and seeded from UtcNow. Fixes the 'click Stop on every restart' UX issue. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes sessions stuck showing "Thinking" forever, adds a "Copilot Console" context menu action, and hardens thread safety in the Dashboard render pipeline.
Changes
Bug Fix: Render Throttle Race (stuck "Thinking")
HandleCompletewas usingScheduleRender()(debounced timer) which could silently drop the final render after session completionCompleteResponse'sOnStateChangedwas droppedHandleCompletenow callsInvokeAsync(StateHasChanged)directly, bypassing both throttle and debounceInvokeAsyncfor thread safety_disposedguards to preventStateHasChangedon disposed componentNew Feature: Copilot Console
copilot --resume <sessionId> --yoloin Terminal.appPlatformHelper.ShellEscape()(single-quote escaping) to prevent shell injectionSecurity: Shell Injection Prevention
ShellEscape()toPlatformHelperfor testability and reuseTests
ShellEscapetests covering plain text, single quotes, special chars, empty stringsCompletionRacetest documenting the throttle race conditionReview
4-model fleet review (Opus 4.6, GPT-5.3 Codex, Sonnet 4.6, Gemini 3 Pro):
completedSessions.RemoveinContinueWithcallback → fixed_disposedguard