Skip to content

Fix three scan-correctness bugs: Trash FDA, duplicate rows, inflated estimate#29

Merged
iliyami merged 1 commit into
mainfrom
fix/scan-correctness
Jun 1, 2026
Merged

Fix three scan-correctness bugs: Trash FDA, duplicate rows, inflated estimate#29
iliyami merged 1 commit into
mainfrom
fix/scan-correctness

Conversation

@iliyami
Copy link
Copy Markdown
Owner

@iliyami iliyami commented Jun 1, 2026

Summary

Three user-reported scanning bugs, two sharing a root cause in TargetedScanner (swallowed permission denials + no de-duplication of overlapping targets).

1. "Trash is empty" on a full Trash

~/.Trash is TCC-protected. Without Full Disk Access, the recursive FileManager.enumerator silently yields nothing while fileExists() still returns true — so the scan looked empty even with a full Trash.

  • TargetedScanner now probes each target root with open(O_RDONLY|O_DIRECTORY) and reports EPERM/EACCES via the new ScanOutcome.permissionDenied (instead of swallowing it).
  • TrashBinsView shows a "Full Disk Access needed" empty-state (Open Settings / Rescan / Done) rather than a false "Trash is empty."

2. Large & Old Files showed the same file twice

LargeOldFilesModule scans ~ recursively and ~/Downloads recursively, so Downloads files were enumerated by both targets and surfaced as duplicate rows. Scan results are now de-duplicated by URL across targets.

3. Cleanup freed less than the estimate (≈2 GB estimated → 934 MB freed)

Same duplicates: the pre-clean estimate summed each duplicate FileItem, while Clean trashes each URL once (the second copy hits a benign "already gone" skip, so freedBytes is correct). selectedSize() now counts each URL once — the "X will be freed" preview matches what actually gets freed, even when a file appears in multiple categories.

Tests (red→green, TDD)

  • testOverlappingTargetsDeduplicateByURL, testSameTargetListedTwiceDeduplicates
  • testPermissionDeniedDirectoryIsReported, testReadableEmptyDirectoryIsNotPermissionDenied (chmod-000 reproduces the EACCES denial)
  • testSelectedSizeCountsEachURLOnce, testSelectedSizeSumsDistinctSelectedItems

Full suite: 394 passed, 4 skipped, 0 failures. Version bumped 1.5.2 → 1.5.3 (sync check passes).

🤖 Generated with Claude Code

…estimate

Root cause shared by two of these: TargetedScanner swallowed permission
denials and never de-duplicated overlapping targets.

1. "Trash is empty" on a full Trash. ~/.Trash is TCC-protected; without
   Full Disk Access the recursive enumerator silently yields nothing and
   fileExists() still returns true, so the scan looked empty. The scanner
   now probes each target root with open(O_RDONLY|O_DIRECTORY) and reports
   EPERM/EACCES via the new ScanOutcome.permissionDenied. TrashBinsView
   surfaces a "Full Disk Access needed" empty-state (Open Settings /
   Rescan) instead of a false "Trash is empty."

2. Large & Old Files listed the same file twice. That module scans ~
   recursively AND ~/Downloads recursively, so Downloads files were
   enumerated by both targets. Scan results are now de-duplicated by URL.

3. Cleanup freed less than the estimate (~2 GB estimated, 934 MB freed).
   Same duplicates: the pre-clean estimate summed each duplicate FileItem,
   while Clean trashes each URL once (the second copy hits a benign
   "already gone" skip). selectedSize() now counts each URL once, so the
   "X will be freed" preview matches reality even when a file appears in
   multiple categories.

Bump 1.5.2 -> 1.5.3.
@iliyami iliyami self-assigned this Jun 1, 2026
@iliyami iliyami merged commit fbf5a0d into main Jun 1, 2026
2 checks passed
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.

1 participant