Skip to content

fix: report an unreadable disk as unread rather than degrading - #2107

Merged
laurentiu021 merged 1 commit into
mainfrom
fix/disk-health-unreadable-is-not-degrading
Sep 4, 2026
Merged

fix: report an unreadable disk as unread rather than degrading#2107
laurentiu021 merged 1 commit into
mainfrom
fix/disk-health-unreadable-is-not-degrading

Conversation

@laurentiu021

Copy link
Copy Markdown
Owner

Closes #2099.

What was wrong

HealthScoreService marked the disk component unavailable only when the list was empty:

if (disks is null || disks.Count == 0) unavailable.Add(DiskComponent);

A drive with no SMART data still produces a DiskHealthReportHealthPercent is documented to return null in that case — so the list is non-empty, the component is not marked unavailable, ComputeDiskScore returns the deliberate unknown 80, and ClassifySmartHealth(80, unavailable: false) lands in the >= 60 branch: "Disk health degrading — check System Health", yellow.

The codebase already ruled that outcome out in two places:

  • DashboardViewModel.ClassifySmartHealth's remarks: "Scoring it as unknown instead would put it in the 'degrading' branch, which is a different wrong answer: nothing is degrading, nothing was measured."
  • UnknownComponentScore_StaysBelowEveryGreenBranch asserts >= 60 with the comment "and must not read as degrading either — nothing was measured" — an intent this path did not deliver.

Not rare: consumer SATA and NVMe drives frequently expose nothing through MSFT_StorageReliabilityCounter, and a VM exposes nothing at all.

The fix

disks.All(d => d.HealthPercent is null) — drives present but none readable is the same absence of evidence as no drives at all.

The decision moved into a pure internal static UnavailableComponents(disks, snapshot), for the same reason ComputeDiskScore is one: it is worth asserting, and asserting it through ComputeAsync would mean querying WMI.

All, not Any, deliberately. With one drive readable at 20% and one unreadable, Any would mark the component unavailable and replace "Disk health critical" with "could not be read" — hiding a drive Windows has already flagged as failing. A mixed read keeps the worst measured verdict.

Verification

Two mutations, restored byte-for-byte and re-hashed:

Mutation Result
back to disks.Count == 0 RED on the new all-unreadable test — Collection: ["Memory", "Uptime"] / Not found: "Disk"
AllAny RED on the mixed-read test (Collection: ["Disk", …] / Found: "Disk") and on the empty-list test

That second row corrected my own prediction and is worth recording: [].All(…) is vacuously true while [].Any(…) is vacuously false, so All covers the no-drives case for free and Any would have silently broken it. Both reasons point the same way.

Baseline and post-restore: 4 green, 0 red. Regression sweep: 230 named tests across HealthScoreServiceTests, DashboardViewModelTests, DiskHealthReport*, SystemHealthViewModelTests and ArchitectureTests — 350 cases green, plus the harness-only author-header case for the throwaway runner. Builds 0 errors / 0 warnings, dotnet format --verify-no-changes clean on both projects, version consistency csproj 1.76.11 = CHANGELOG 1.76.11 = SECURITY 1.76.x.

Docs

No README change: the tab's behaviour is described in terms of what it reports, and the README does not name the degrading wording. CHANGELOG entry included.

HealthScoreService marked the disk component unavailable only for an EMPTY list.
A drive with no SMART data still produces a report (HealthPercent returns null),
so the list is non-empty, ComputeDiskScore returns the deliberate unknown 80, and
ClassifySmartHealth reads 80 with unavailable=false through its >= 60 branch as
"Disk health degrading" -- a claim about failing hardware on a machine where
nothing was measured.

Both the classifier's own remarks and UnknownComponentScore_StaysBelowEveryGreenBranch
already ruled that outcome out in prose while this path delivered it.

Fixed with disks.All(d => d.HealthPercent is null), and the decision extracted into
a pure internal UnavailableComponents so it can be asserted without querying WMI.

All, not Any: with one drive failing at 20% and one unreadable, Any would replace
"Disk health critical" with "could not be read" and hide the failing drive. The
mutation proof also showed Any breaks the empty-list case, since [].Any() is false
while [].All() is true.

Closes #2099
@laurentiu021
laurentiu021 merged commit f39b84a into main Sep 4, 2026
5 checks passed
@laurentiu021
laurentiu021 deleted the fix/disk-health-unreadable-is-not-degrading branch September 4, 2026 07:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Dashboard — a disk whose health cannot be read is reported as "degrading"

1 participant