fix(threading): harden the captured-UI-context exit of IsCompleted and settle AC5 - #890
Merged
drmoisan merged 14 commits intoSep 14, 2026
Conversation
…lan and spec write set
…he orchestrator index
# Conflicts: # .claude/agent-memory/atomic-executor/MEMORY.md # .claude/agent-memory/atomic-planner/MEMORY.md # .claude/agent-memory/orchestrator/MEMORY.md
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YZxqEe1udErQiYYt6Bb2d
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YZxqEe1udErQiYYt6Bb2d
…etry assertions Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YZxqEe1udErQiYYt6Bb2d
…dings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YZxqEe1udErQiYYt6Bb2d
…dence Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YZxqEe1udErQiYYt6Bb2d
…d settle issue 809 AC5 (#816) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YZxqEe1udErQiYYt6Bb2d
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YZxqEe1udErQiYYt6Bb2d
…d in the 816 review to issue #889
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.
Suggested title
fix(threading): harden the captured-UI-context exit of
IsCompletedand settle issue #809's AC5Summary
_uiSyncContextexit ofSynchronizationContextAwaiter.IsCompletedinUtilitiesCS/Threading/UiThread.csso it returnstrueonly when the captured context matches and a non-null captured UI dispatcher is reference-equal to the executing thread's dispatcher.QuickFiler.Viewers.SyncContextFormon an MTA thread was measured to complete without throwing on this host.Initialize()is already correctly implemented in production; only the test's apartment premise and message discrimination needed tightening).UtilitiesCS/Threading/UiThread.csline coverage from 96.03% (121/126) to 97.74% (130/133); the newly-hardenedreturn true;line moves from uncovered to covered.Why
Issue #809 hardened three related defects in
UtilitiesCS/Threading/UiThread.csbut left two residuals: (1) the_uiSyncContextexit ofIsCompletedstill admitted a thread with no captured dispatcher because a null-to-nullReferenceEqualscomparison passes, and (2) AC5 of #809's own specification — a runtime measurement of whether constructingSyncContextFormon an MTA thread throws — was never actually measured, only asserted from the issue #782 narrative. This PR closes both residuals under issue #816.What Changed
Core fix (
UtilitiesCS/Threading/UiThread.cs,+15/-2, confined to the_uiSyncContextexit and its comment; the other four exits of the five-exit accessor are byte-identical):Tests (new file
UtilitiesCS.Test/Threading/UiThreadApartmentMeasurement_Tests.cs, plus one addition toUtilitiesCS.Test/Threading/UiThread_Tests.csand two assertion tightenings inUtilitiesCS.Test/Threading/UiThreadInitContract_Tests.cs):UiThreadPredicateHardening_Tests— two negative cases: a recycled thread-id with a foreign dispatcher, and a null captured dispatcher with none on the executing thread. Both are red against the unmodified predicate and green after.UiThreadApartmentMeasurement_Tests.SyncContextFormShow_OnAThreadMeasuredAsMta_RecordsTheOutcome— the runtime measurement for issue Bug: uithread-init-contract-residuals-784-787-788 #809's AC5, run on a dedicated MTA thread with the form disposed in afinallyand hidden from the taskbar/minimized beforeShow(), so no window is displayed in an unattended run.ApartmentState.STAassertion and a.WithMessage(...)constraint so it can no longer pass for the wrong reason.Docs: this feature folder's
spec.md/issue.md/user-story.mdacceptance criteria checked off (AC1-AC14), plus issue #809's ownspec.mdAC5 checkbox, plus the AC5 measurement artifact mirrored into issue #809's evidence folder.Architecture / How It Fits Together
No new type or seam. The change is a single added conjunct plus a null guard on one existing exit of a five-exit accessor inside
UtilitiesCS.Threading.UiThread.UiThread.Init()/Initialize()are unchanged (verified by an anchored diff against a pre-change ref showing zero lines removed from the initializer).Verification
Completed (see
docs/features/active/2026-09-08-uithread-iscompleted-branch2-residual-and-ac5-apartment-measurement-816/evidence/and the policy-audit/code-review/feature-audit artifacts dated 2026-09-14T00-40 in the same folder):dotnet tool run csharpier format ./check .— clean, no file rewritten on the final pass.msbuild /t:Rebuild ... /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true— 0 errors.msbuild /t:Rebuild ... /p:TreatWarningsAsErrors=true(nullable) — 0 errors, no/p:Nullable=enable.vstest.console.exeoverUtilitiesCS.Test+QuickFiler.Testwith coverage — 6336/6336 passed, run three times.UtilitiesCS/Threading/UiThread.cs: 96.03% → 97.74% (CLAUDE.md floor is 80%); repo-wide first-party line coverage 81.51% → 81.52%.get_IsCompletedaccessor'sline-ratemoved from 0.9 to 1.0 andbranch-ratefrom 0.917 to 1.0.Recommended: none beyond the above; this is a self-contained hardening with full regression coverage.
Backward Compatibility / Migration Notes
No breaking change. No public API changed. The accessor's five-exit structure and source order are unchanged; only the
_uiSyncContextexit's condition is stricter, which can only turn a priortrueintofalsefor callers that previously matched context identity with a null captured dispatcher — a state issue #809 already establishes as a defect rather than an intended caller path.Risks and Mitigations
_dispatcheris ever null on a fully-initialized instance. Mitigation:Init()/Initialize()are unchanged by this PR (verified by diff), so_dispatcheris populated on every successful initialization exactly as before; the new conjunct only changes behavior for the previously-defective null/foreign-thread case.Review Guide
UtilitiesCS/Threading/UiThread.cs— the one-exit hardening (smallest, highest-value read).UtilitiesCS.Test/Threading/UiThreadApartmentMeasurement_Tests.cs— new file, both hardening-negative tests and the AC5 measurement test.UtilitiesCS.Test/Threading/UiThread_Tests.csandUiThreadInitContract_Tests.cs— the positive twin and the two assertion tightenings.docs/features/active/2026-09-08-.../spec.mdanddocs/features/active/2026-09-07-.../spec.md— acceptance-criteria check-offs, for traceability only.Follow-ups
IsCompletedhas the same null-ReferenceEquals-null defect shape one exit below the one hardened here; out of scope for Bug: uithread-iscompleted-branch2-residual-and-ac5-apartment-measurement #816 because AC1 forbids touching any exit but_uiSyncContext.Meziantou.Analyzer<Analyzer Include>paths naming 3.0.203 whilepackages.confignames 3.0.235 in some projects) was worked around locally without touching tracked files; it reproduces onorigin/mainindependent of this PR and is worth its own follow-up issue.GitHub Auto-close