Harden all clipboard paths against a busy Windows clipboard - #416
Merged
Conversation
The Windows clipboard is a shared resource; when another process holds it, SetTextAsync throws CLIPBRD_E_CANT_OPEN, and an unguarded await in an async void handler crashes the app (issue #415, Robot Advice Copy to Clipboard). - New ClipboardHelper: all reads/writes retry briefly and never throw - Convert all 15 write sites and both paste sites in PlanViewer.App - Replace DataGrid's built-in Ctrl+C (unguarded inside Avalonia) with a guarded copy on all four grids, mirroring its exact key/modifier gate - Intercept TextBox Copy/Cut/Paste app-wide via class handlers; Avalonia's own implementations are unguarded async void / TimeoutException-only - Cut (editor and TextBoxes) only deletes the selection after the text actually reached the clipboard - Clipboard-busy feedback on Advice window, Copy Repro, and MCP copy button - Log AppDomain/TaskScheduler unhandled exceptions to LocalApplicationData\PerformanceStudio\crash.log Fixes #415 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 28, 2026
Merged
Merged
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 the crash in #415: clicking Copy to Clipboard in the Robot Advice window while the Windows clipboard was locked by another process (clipboard managers, RDP, Office clipboard sync) threw a COMException (CLIPBRD_E_CANT_OPEN) out of an async void handler and killed the app.
The reported button was one of 15 write sites with the identical unguarded pattern, and two more instances of the same crash class live inside Avalonia itself (DataGrid built-in Ctrl+C and TextBox Copy/Cut/Paste, both verified unguarded against the decompiled 11.3.x binaries). This PR closes all of them.
ClipboardHelper: every clipboard read/write resolves the TopLevel, retries up to 3 times with a 100ms pause while the clipboard is locked, and returns false/null instead of throwing. All 15 write sites and both paste sites in PlanViewer.App now go through it; nothing outside the helper touches the raw clipboard API (grep-verified).DataGridBehaviors.AttachCopyGuard: tunnel-phase Ctrl+C/Ctrl+Insert interception on all four DataGrids (Query Store results, statements, history, settings format grid), mirroring the built-in copy's exact key/modifier gate (platform command modifier, no Shift/Alt) so behavior is identical minus the crash. In-cell editors keep their own Ctrl+C.TextBoxClipboardGuard: app-wide class handlers for TextBoxCopyingToClipboard/CuttingToClipboard/PastingFromClipboard, which Avalonia raises before touching the clipboard. Covers every TextBox in the app including ones created later.CanCopy/CanCut/CanPastegates preserve password-box and read-only semantics.CrashLogger+AppDomain.UnhandledException/TaskScheduler.UnobservedTaskExceptionwiring in Program.cs: any future crash-to-desktop leaves a stack trace in%LOCALAPPDATA%\PerformanceStudio\crash.log(1MB cap) instead of requiring Event Viewer archaeology like this report did.Changes
Services/ClipboardHelper.cs(new): never-throwing TrySetTextAsync/TryGetTextAsync with retryServices/TextBoxClipboardGuard.cs(new): global TextBox copy/cut/paste interception, registered in App.axaml.csServices/CrashLogger.cs(new) +Program.cs: last-resort exception loggingHelpers/DataGridBehaviors.cs: new AttachCopyGuard, attached in QueryStoreGridControl, PlanViewerControl, QueryStoreHistoryControl, SettingsWindowServices/AdviceWindowHelper.cs,AboutWindow,MainWindow.*,PlanViewerControl.*,QuerySessionControl.*,QueryStoreGridControl.Selection,QueryStoreHistoryControl: all call sites converted to the helperQueryStoreGridControl.Selection.cs: Copy Row format extracted toFormatRowForClipboard, shared by the context menu and Ctrl+CTest Plan
dotnet buildPlanViewer.App: 0 warnings, 0 errorshold-clipboard.ps1 (locks the clipboard like RDP/clipboard managers do)
Closes #415
Generated with Claude Code