fix(quickfiler): skip the metrics write when no diagnostic lines survive (#646) - #718
Merged
drmoisan merged 8 commits intoSep 1, 2026
Merged
Conversation
…ay guard Preparation-only outputs for issue 646. This commit modifies no production or test source. issue.md adds the Acceptance Criteria section (AC1-AC8) that the minor-audit contract requires as the sole acceptance-criteria source; the scaffolded file carried none. plan.2026-08-31T20-04.md is the three-phase minimal-audit plan, validator clean, cleared preflight in two rounds. research/ holds the task-researcher findings plus an orchestrator correction recording that origin/main advanced mid-run and superseding the citations that change invalidated. Planned footprint at execution: one early-return guard in QuickFiler/Controllers/QfcHomeController.Metrics.cs, one regression test in QuickFiler.Test/Controllers/QfcHomeControllerMetricsTests.cs, and this feature folder. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ATYLDoRLKXS5sgAzegW7ZL
…eparation Captures that a parallel sibling described as pending can merge during a preparation run, moving origin/main under a subagent that is already reading the tree, and that git status stays clean while it happens. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ATYLDoRLKXS5sgAzegW7ZL
…-writes-empty-session-file-646
…erion The preparation run recorded an orchestrator memory entry on this branch. Acceptance criterion AC7 restricts the branch diff to the two owned source files and the feature folder, so the entry is reverted here. Its content is carried in the run report for re-filing from a separate branch after merge. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ATYLDoRLKXS5sgAzegW7ZL
WriteMetricsAsync filtered null and whitespace diagnostic lines but then awaited MetricsFileWriter unconditionally. The default writer opens the target for append, so a session producing no diagnostic content created a zero-content session-metrics file, or refreshed the last-write timestamp of an existing one. Add the same early return the EmailFiler path already carries in EfcHomeController.Metrics.cs, placed between the filter and the writer call. The MetricsFileWriter delegate signature and the failure-logging branch are owned by issue 647 and are unchanged here. Covered by a new MSTest regression that stubs an all-null-or-whitespace diagnostics array and asserts the injected writer is never invoked. It fails against the unguarded implementation and passes after the guard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ATYLDoRLKXS5sgAzegW7ZL
Phase 0 baseline, Phase 1 fail-before/pass-after regression evidence, and Phase 2 final-QC gate artifacts for the metrics empty-array guard, plus the plan checklist and the eight acceptance criteria checked off against them. Coverage evidence is committed as package-level JaCoCo projections rather than the raw Cobertura the runner emits, following the precedent set in d0955dc. Each projection reconciles exactly to its source root lines-covered and lines-valid, and coverage-artifact-substitution records the conversion so the gate sequence stays auditable. The measured line-rate is labelled as a single-assembly unfiltered denominator. Only QuickFiler.Test ran, and the denominator includes vendored third-party assemblies and the test assembly itself, so the figure is not the repository-wide policy denominator. The no-regression comparison holds because both sides were produced by the identical invocation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ATYLDoRLKXS5sgAzegW7ZL
Policy audit, code review, and feature audit for the metrics empty-array guard. Verdict is PASS with zero blocking findings and all eight acceptance criteria re-derived against evidence rather than accepted from checkboxes. Six non-blocking findings are recorded with their reachability. Two are worth carrying forward: GetMoveDiagnostics still has no null-array guard, which no production implementation can currently reach, and the calendar appointment is written before the new early return, so an empty-diagnostics session still creates one. The latter is not a regression, since that session previously produced the appointment and an empty file. Three coverage rows are recorded FAIL rather than softened. Two are absent measurement artifacts that predate this branch, and the third is the changed file at 77.60 percent against an 85 percent floor, where the uncovered lines are Outlook-interop paths that AC7 forbids this branch from touching. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ATYLDoRLKXS5sgAzegW7ZL
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.
fix(quickfiler): skip the metrics write when no diagnostic lines survive (#646)
Summary
QfcHomeController.WriteMetricsAsyncfiltered null and whitespace diagnostic lines but then awaitedMetricsFileWriterunconditionally, so a session producing no diagnostic content still created a zero-content session-metrics file, or refreshed the last-write timestamp of an existing one.EfcHomeController.Metrics.cs.MetricsFileWriterdelegate signature and theif (!metricsWritten)failure branch, both owned by Bug: fileio2-write-retry-reports-success-on-final-failure #647, are untouched.Why
The QuickFiler and EmailFiler metrics writers diverged. The EFC path acquired its empty-array guard when its writer was extracted behind
_dependencies.MetricsLineWriter; the QFC path acquired its writer seam later, during #442, and the guard was not carried across.This is a narrow regression rather than a long-standing defect. Before the #442 flush fix the QFC metrics queue was never drained, so nothing was ever written and the empty-array case could not manifest. Making the flush work made it reachable.
The guard was deliberately not applied inside the #442 feature. That work was complete and its toolchain green when the finding was raised in review, and the General Code Change Policy directs opening a new issue rather than widening scope in flight.
What Changed
Core fix
QuickFiler/Controllers/QfcHomeController.Metrics.cs— early return when the filtered diagnostic-line array is empty, placed immediately after the filter statement and before theCancellationToken.Nonecomment, so that comment stays adjacent to the writer statement it explains.Tests
QuickFiler.Test/Controllers/QfcHomeControllerMetricsTests.cs— addsWriteMetricsAsync_WithAllNullOrWhitespaceDiagnostics_DoesNotInvokeWriter. The two pre-existing metrics tests are provably unmodified: the test-file diff contains zero removal lines.Docs and evidence
Architecture / How It Fits Together
WriteMetricsAsynccomputes diagnostics throughIQfcCollectionController.GetMoveDiagnostics, filters null and whitespace entries, then awaits the injectableMetricsFileWriterseam, which defaults toFileIO2.WriteTextFileAsync. That default opens the target for append, which is why an empty array still had a filesystem effect. The new guard returns before the seam is reached, so no writer implementation is invoked at all rather than being invoked with an empty payload.Verification
Completed
dotnet tool run csharpier format .thendotnet tool run csharpier check .— exit 0. The format pass rewrote the new test's chained assertion, so the QC loop was restarted; pass 2 was a fixpoint.msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true— exit 0,Build succeeded, 5 warnings, 0 errors.msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true— exit 0,Build succeeded, zeroCS86xx.vstest.console.exe QuickFiler.Test\bin\Debug\QuickFiler.Test.dll /EnableCodeCoverage— exit 0, 1285 passed of 1285 (baseline was 1284 of 1284).Failed: 1, 346 ms, failing on the test's own assertion message withinvokedobserved asTrue.Passed: 1. The two pre-existing tests:Passed: 2.Coverage
New-code coverage on the guard is 3 of 3 measurable lines. The closing brace emits no
<line>entry in the report; this was verified structural by comparing against the identically shaped EFC guard in the same report, whose closing brace also emits no entry in long-standing fully-exercised code.Two qualifications a reviewer should not skip:
line-rateof 0.3405 is not a repository-wide or policy coverage figure. OnlyQuickFiler.Test.dllwas run, and the denominator includes eight vendored third-party assemblies and the test assembly itself. The no-regression comparison is still valid, because baseline and final came from the identical invocation, and it moved up (0.3404862683to0.3405230596). The first-party subset of the same run moved from 23.4022% to 23.4059%.condition-coverageoccurrences and a rootbranch-rate="1", so the zero BRANCH counters in the committed projections are faithful rather than a parse failure. The guard's two branch outcomes are covered behaviourally instead: the true path by the new test, the false path by the two pre-existing non-empty tests.Coverage evidence is committed as package-level JaCoCo projections rather than the raw Cobertura the runner emits, following the precedent in
d0955dc4. Each projection reconciles exactly to its source rootlines-coveredandlines-valid(48426/142226 baseline, 48436/142240 final).evidence/qa-gates/coverage-artifact-substitution.2026-09-01T16-41.mdrecords the conversion so the gate sequence stays auditable.Recommended
Backward Compatibility / Migration Notes
No breaking changes. No public API, signature, or file was renamed or removed. The only behavioural change is the absence of a filesystem write in a case that previously wrote nothing meaningful.
Risks and Mitigations
WriteMetricsAsync_InvokesInjectedMetricsFileWriterOncecontinues to prove the non-empty path still writes.Review Guide
QuickFiler/Controllers/QfcHomeController.Metrics.cs— the four added lines.QuickFiler.Test/Controllers/QfcHomeControllerMetricsTests.cs— the one added test.evidence/qa-gates/coverage-delta-verification.2026-08-31T20-04.md— the coverage argument and its explicit denominator caveats.evidence/qa-gates/coverage-artifact-substitution.2026-09-01T16-41.md— why the committed coverage evidence is a projection.The remaining 27 paths are evidence artifacts and can be skimmed.
Follow-ups
None are promoted from this branch, because AC7 restricts the diff to a named path set. They are listed here for consolidation after merge:
GetMoveDiagnosticsstill has no null-array guard, so a narrower null asymmetry with the EFC reference survives. Not reachable today: it needs anIQfcCollectionControllerimplementation returningnull, and the sole production implementation does not.WriteMoveToCalendarruns before the new guard, so an empty-diagnostics session still creates an Outlook calendar appointment. Reachable on every occurrence of the reported input, but not a regression: that session previously produced the appointment and an empty file. Whether the appointment should also be suppressed is a product decision, and both AC1 and the issue's Expected Behavior are scoped toMetricsFileWriter.TimeProvideratTimeProvider.System. No assertion depends on the clock, and this matches every sibling test.quality-tiers.ymldoes not exist at the repository root though.claude/rules/quality-tiers.mddeclares it the source of truth, so tier-dependent gates are unevaluable.CLAUDE.mdUT2 also states an 80/90 coverage floor while.claude/rulesstates a uniform 85/75. Both are pre-existing and repository-wide.GitHub Auto-close
The PR-context bundle proposed
#442,#647and#CR-1alongside#646, and reported the GitHub CLI as unavailable. That report is incorrect: the CLI is present and was queried directly.#442and#647are both alreadyCLOSEDand are cited in this item's prose only as context;#CR-1is a code-review finding identifier, not an issue. Only #646 is closed here.