Conversation
Opening Settings → Advanced could freeze KeyPath behind a spinner indefinitely. detectDuplicateAppCopies() ran mdfind with an unbounded waitUntilExit(), bypassing SubprocessRunner, and every caller invoked it synchronously from main-actor UI code. When Spotlight stalled (observed with KeyPath.app copies indexed on an external drive), the whole window hung until the mdfind process was killed by hand. - detectDuplicateAppCopies() is async and runs mdfind through SubprocessRunner with a 2s timeout; timeout, failure, or an empty result falls back to the canonical install locations. - Settings, Advanced settings, and both wizard pages await it; the synchronous copyPublishedStatus() folds the result in from a follow-up task so its observer contract is unchanged. - Sparkle_generate_appcast/ staging copies are excluded from the duplicate report alongside the existing build directories. - Tests for the bounded scan and the timeout fallback; bug note in docs/bugs/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
One test-coverage concern in this diff:
let copies = await HelperMaintenance.shared.detectDuplicateAppCopies()
XCTAssertTrue(copies.allSatisfy { $0.hasSuffix("/KeyPath.app") })
XCTAssertTrue(copies.allSatisfy { FileManager.default.fileExists(atPath: $0) })
Suggest adding Everything else in the diff (moving |
Summary
Opening Settings → Advanced could freeze KeyPath behind a spinner indefinitely. Observed on a real install: the debug log stopped mid-validation and a single
mdfind kMDItemFSName == 'KeyPath.app'cchild ran for 70s+; killing it unblocked the app instantly (the same query run by hand returned in 0.18s).Two defects compounded:
HelperMaintenance.detectDuplicateAppCopies()ranmdfindwith an unboundedwaitUntilExit(), bypassingSubprocessRunnerand its timeout.AdvancedSettingsTabView.task,SettingsView.copyPublishedStatus(), both wizard pages'.onAppear), so a slow Spotlight query froze the whole window.Changes
detectDuplicateAppCopies()isasyncand runsmdfindthroughSubprocessRunnerwith a 2s timeout. Timeout, failure, non-zero exit, or empty output fall back to the canonical install locations, and every fallback path now logs.awaitit. The wizard pages use.taskso a dismissed page cancels its scan (andSubprocessRunnerkills themdfind).copyPublishedStatus()stays synchronous — its validation observer contract (SettingsStatusValidationLintTests) is unchanged — and folds the scan in from a follow-up task, throttled to once a minute./Sparkle_generate_appcast/staging copies are excluded from the duplicate report.docs/bugs/2026-09-21-duplicate-app-scan-mdfind-hang.md.Installer/repair matrix: no row affected — duplicate detection is diagnostic only (it logs a warning); no repair decision depends on it.
Test plan
swift build(Xcode 27 pin)HelperMaintenanceTests.testDetectDuplicateAppCopiesUsesBoundedSpotlightScan,testDetectDuplicateAppCopiesFallsBackWhenSpotlightTimesOutSubprocessRunnerTests.testRealRunnerTimeoutTerminatesLongRunningProcess— real runner vs/bin/sleep 10with a 0.5s budget (returns in ~0.52s)HelperMaintenanceTests|InstallerEngineEndToEndTests|SettingsStatusValidationLintTests|SubprocessRunnerTests— 47 passed./Scripts/test-full.sh):KeyPathTests4292 tests, 0 failures.KeyPathSnapshotTests: 3 failures, pre-existing on master and unrelated —EasyViewSnapshotTests.testHomeRowTiming{Slider,PerFinger,FastTyping}render 1300×586 vs a 1300×638 reference; the references date from 2026-06-07 (Prefer privileged helper in debug app builds #803) andHomeRowTimingSectionchanged layout on 2026-07-25 (Make home row timing mode-aware #1222). This PR does not touch that view.Review gate
./Scripts/review-gate.shran locally (exit 0). Dispositions:.onAppear+Taskleaks the scan past view teardown — fixed (.task).refreshDuplicateAppCopies.WizardSignatureHealthCheck.isRunningAdHoc()runscodesignwithwaitUntilExit()— same anti-pattern, out of scope; worth a follow-up.@MainActorprotocol satisfied by anonisolated asyncmethod — builds clean under Swift 6 language mode (-swift-version 6), no new warnings.🤖 Generated with Claude Code