bug(798): fail loudly on QuickFiler column-add timeout and guard the ribbon boundary - #800
Merged
drmoisan merged 4 commits intoSep 7, 2026
Conversation
Promote the potential bug entry for the QuickFiler launch crash on folder T&E and prepare the full-bug lifecycle documents for issue #798. AddQfcColumnsAsync swallows three consecutive 3-second timeouts, the ETL then proceeds with Outlook's five default columns, and the row builder indexes the missing SentOn key. The resulting AggregateException is rethrown with throw e and escapes an async void ribbon handler with no boundary catch. Adds the research record, spec.md carrying the maintainer's AC1 through AC6 verbatim plus eight supplementary criteria, user-story.md, and a 93-task atomic plan cleared through four preflight rounds. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses issue #798. Production changes: - UtilitiesCS/Extensions/DfDeedle.cs and the new partial UtilitiesCS/Extensions/DfDeedle.QfcColumns.cs relocate the QFC column-add path, bound it with a cancellable timeout, and validate required columns with an actionable message instead of a bare KeyNotFoundException. - QuickFiler/Controllers/QfcDatamodel.cs and QuickFiler/Controllers/QfcDatamodel.FrameBuilding.cs use a stack-preserving rethrow. - TaskMaster/Ribbon/RibbonCommandBoundary.cs is a new catching boundary for the three named async void ribbon handlers in TaskMaster/Ribbon/RibbonViewer.cs, which now route through it. Test changes: - UtilitiesCS.Test/Extensions/DfDeedleQfcColumnTimeoutTests.cs - UtilitiesCS.Test/Extensions/DfDeedleRequiredColumnValidationTests.cs - UtilitiesCS.Test/Extensions/DfDeedle_COM_Tests.cs - TaskMaster.Test/Ribbon/RibbonCommandBoundaryTests.cs - QuickFiler.Test/Controllers/QfcDatamodelRethrowTests.cs Compile entries for the six new source files were added to the UtilitiesCS, TaskMaster, UtilitiesCS.Test, TaskMaster.Test and QuickFiler.Test project files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses issue #798. Records the Phase 7 through Phase 9 audit trail for the QuickFiler column-add timeout fix. No production or test source is changed by this commit; the sixteen-path write set was committed in 4a29d7e and the anchored write-set diff still resolves to exactly 16 paths. Final QC toolchain loop, clean on pass 1, all four steps exit 0: - csharpier format then check: Checked 1593 files, exit 0 - analyzer build: 0 Error(s), 0 Warning(s) - nullable build: 0 Error(s), 0 Warning(s) - full-suite run with coverage: 7048 total, 7048 passed, 0 failed, 0 skipped Change-scoped coverage obligations all pass: - UtilitiesCS/Extensions/DfDeedle.QfcColumns.cs rate 0.9762 - TaskMaster/Ribbon/RibbonCommandBoundary.cs rate 0.9016 - relocation-adjusted covered lines 157 + 164 = 321 against a baseline of 226 for the source file the new partial was split from - the three ExcludeFromCodeCoverage production files read NOT INSTRUMENTED on both sides and are recorded NOT APPLICABLE Acceptance criteria: 13 of 14 met and checked off in spec.md. AC6 remains unchecked and is recorded PENDING MANUAL, because it requires live-Outlook verification on the reproduction folder that no automated test can supply; its manual steps are handed off in evidence/other/ac6-manual-verification-handoff.md. Three follow-up findings are recorded in evidence/other/followup-promotions.md for promotion after merge. The promotion route was deliberately not exercised on this branch, because creating a promotion file here would add a seventeenth path and falsify the AC13 write-set gate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses issue #798. Adds the policy-audit, code-review and feature-audit artifacts for the QuickFiler column-add timeout fix. No production or test source is changed by this commit; the anchored write-set diff still resolves to exactly 16 paths. Verdict: PASS. Blocking findings: 0. Non-blocking findings: 11. No remediation cycle was opened. Acceptance criteria: 13 PASS, 0 PARTIAL, 0 FAIL, 1 UNVERIFIED. The UNVERIFIED criterion is AC6, which requires live-Outlook verification on the reproduction folder and is recorded PENDING MANUAL. spec.md was not modified by the review. The review records one obligation that no executor artifact reported: repository-wide branch coverage reads 0.6612 against the 0.75 uniform floor in the quality-tiers rule, up from a baseline of 0.6605. Both figures are read from the root coverage element of the two committed Cobertura documents. The condition is pre-existing and moves in the correct direction, so it is dispositioned Non-blocking. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.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 the QuickFiler launch crash on a folder named "T&E".
AddQfcColumnsAsyncswallowed three consecutive 3-second timeouts and returned normally, the ETL then proceeded with Outlook's five default columns, and the row builder indexed the missingSentOnkey. The resultingAggregateExceptionwrappingKeyNotFoundExceptionwas rethrown withthrow e, losing the original stack, and escaped anasync voidribbon handler with no boundary catch, surfacing to Outlook as an unhandled exception.Closes #798.
Root cause
The defect was a reporting gap, not a single bad line. Between the accept point and the observed symptom there was no guard at all:
GetEmailDataInViewAsyncawaitedAddQfcColumnsAsync, which returns a bareTask. A completed await was the only success signal available, and it validated nothing about the resulting column set.counter < 2guard was false, thecatch (TimeoutException)body was empty, and the method completed successfully. No exception, no return value, no log line — the method had no failure channel at all.Email2dToRecordsindexedcolumnInfo["SentOn"]. The exception named neither the folder nor the timed-out step, so the symptom was unrelatable to the cause.A second, independent defect compounded it:
TimeoutAfterdoes not cancel the underlying task, and the retry started a newTask.Runper attempt, so up to threeAddQfcColumnsinvocations could run concurrently against one COMTablefrom pool threads.What changed
Six production files, five test files, five project compile-entry files — 16 paths, fixed by AC13 and verified by an anchored diff against the merge base.
UtilitiesCS— the four column methods were relocated verbatim into a new partial,DfDeedle.QfcColumns.cs. The recursive retry was replaced by a single held task re-deadlined up to three times, so the work starts exactly once and the deadline is re-applied to that same instance; exhaustion now throws a descriptive exception naming the folder and the step. Per-step timing instrumentation was added. A newValidateRequiredEmailColumnsguard is called from both the asynchronous and the synchronous entry points, closing a duplicate unchecked indexing path that the async fix alone would have missed.QuickFiler—throw e;becamethrow;at all three sites in theQfcDatamodelpartial family, preserving the original stack.TaskMaster— a newRibbonCommandBoundarytype wraps the three named ribbon handlers, logging with full detail and presenting a dialog that renders inner-exception detail, so anAggregateExceptionno longer presents as "One or more errors occurred." alone.The existing four
TimeoutAfteroverloads are unchanged and no new overload was added. NoTask.DelayorThread.Sleepwas introduced anywhere.Verification
Full toolchain, clean on the first pass with no restart:
dotnet tool run csharpier check .Checked 1593 files in 6008ms./t:Rebuildwith analyzers enforced0 Error(s),0 Warning(s)/t:Rebuildwith warnings as errors0 Error(s),0 Warning(s)The test count reconciles against the 7023-test baseline plus the 25 tests this change adds.
/t:Rebuildwas used deliberately: a warm/t:Buildreturns exit 0 withCoreCompileskipped on every project and runs no analyzers. 19 projects and 71 of 74CoreCompileexecutions confirm the analyzers actually ran.Every acceptance criterion carries fail-before / pass-after evidence under the feature folder's
evidence/tree.Coverage
Change-scoped obligations, all met:
DfDeedle.QfcColumns.cs— 164/168 lines, rate 0.9762 (obligation: >= 0.90)RibbonCommandBoundary.cs— 55/61 lines, rate 0.9016 (obligation: >= 0.90)Repository-wide line coverage moved up, 0.85827 to 0.85869.
Review guide
Four things a reviewer should know before reading the generated context artifact, because the collector reports them incorrectly:
ghon its PATH;ghis installed and authenticated.Normalized result: fail, and none of them is a failure. Each of those artifacts records several gates but carries a single file-levelExpectedExitCode: 1that describes its last gate — a scoped test run that legitimately exits 1 because later-phase tests were still red at that point. The collector pairs the file-level expectation with the first command it parses, which exits 0, so the row inverts. This is the documented per-file limitation of the expectation field; the correct authoring is one artifact per gate needing a non-zero expectation. It is recorded as a non-blocking nit rather than repaired, because rewriting committed evidence after the fact would be worse than annotating it.Feature review
Verdict PASS — 0 blocking findings, 11 non-blocking.
The review surfaced one obligation that no execution artifact reported: repository-wide branch coverage reads 0.6612 against the 0.75 uniform floor, up from a 0.6605 baseline. The coverage evidence reported line coverage only, on both sides. The condition is pre-existing and moves in the correct direction, so it is dispositioned non-blocking, but it was invisible until the reviewer parsed the root element of both Cobertura documents directly.
The review also identified exactly what would close the
RibbonCommandBoundarymargin: the six uncovered lines are four untested scenarios the unit-test policy requires — a throwing log sink (the current test throws from the presentation sink only), anAggregateExceptionwrapping nothing, and the twoArgumentNullExceptionguards. Four small tests take the file to 61/61.Outstanding
evidence/other/ac6-manual-verification-handoff.md. This is the only one of the 14 criteria not met: 13 met, 1 pending manual verification.evidence/other/followup-promotions.mdfor promotion after merge: an unreachablecatch (TimeoutException)in the tworepeatAttemptstimeout overloads, an unguarded shared-static seam mutation in an existing COM test class, and that same class's pre-existing 500-line-cap violation. They were deliberately not promoted on this branch: creating a promotion file here would add a seventeenth path and falsify the write-set gate this change asserts.Risk
The behavioural change is deliberate and worth stating plainly: a column-add that exhausts its budget now throws where it previously returned normally. Callers that relied on the silent-success path will now see an exception — which is the point of the fix, since that silent success is what produced the crash three layers away. The narrow
catch (OperationCanceledException)inQfcHomeController.LaunchAsyncwas left exactly as narrow as it was, so user cancellation stays silent and does not reach the new dialog.