Skip to content

Repair Sparkle updater preflight#6004

Open
lawrencecchen wants to merge 5 commits into
mainfrom
issue-5123-sparkle-cache-install
Open

Repair Sparkle updater preflight#6004
lawrencecchen wants to merge 5 commits into
mainfrom
issue-5123-sparkle-cache-install

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • strip stale quarantine xattrs from bundled Sparkle helpers before starting Sparkle
  • stop pre-creating Sparkle's empty Installation cache directory; remove empty stale copies so Autoupdate owns per-install directory creation
  • add red/green regression coverage for both preflight behaviors

Testing

  • swift test --package-path Packages/CmuxUpdater --filter SparkleUpdatePreflightTests
  • swift test --package-path Packages/CmuxUpdater
  • CMUX_SKIP_ZIG_BUILD=1 ./scripts/reload-cloud.sh --tag up5123

Closes #5123


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Medium Risk
Touches filesystem xattrs and Sparkle cache paths on the real user cache before every update check; behavior change is intentional but could affect edge cases around in-progress installs if an empty Installation dir is removed at the wrong time (mitigated by only deleting when empty).

Overview
Replaces the old Sparkle installation-cache workaround with a dedicated SparkleUpdatePreflight step that runs before updater startup and update checks.

Quarantine cleanup: Before Sparkle runs, the preflight walks bundled Sparkle.framework and strips com.apple.quarantine extended attributes so Autoupdate and other helpers are not blocked by stale download quarantine.

Installation cache policy flip: The controller no longer pre-creates Sparkle’s Installation cache directory under ~/Library/Caches/.../org.sparkle-project.Sparkle/. Instead it removes a stale path when it is a file or an empty directory, leaving Sparkle/Autoupdate to create the directory per install session.

Regression tests cover quarantine removal and empty installation-directory cleanup, with small test fixtures/helpers for bundles and xattrs.

Reviewed by Cursor Bugbot for commit a4f276a. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Repairs the Sparkle updater preflight so Autoupdate starts reliably. Removes stale quarantine flags and cleans invalid Installation cache paths so updates aren’t blocked or flaky.

  • Bug Fixes

    • Strip com.apple.quarantine from Sparkle.framework and its bundled helpers before Sparkle starts and on each check.
    • Replace the old installation-cache workaround; delete empty Installation directories or wrong-type files so Sparkle creates the cache per install.
    • Run the preflight at updater start and before update checks; add regression tests.
  • Refactors

    • Split preflight test helpers into ExtendedAttributeTestSupport and PreflightFixture, and align the fixture with bundle/caches policy.

Written for commit a4f276a. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added a pre-installation preflight that clears quarantine markers from bundled updater components and prepares the updater cache directory.
  • Bug Fixes

    • Replaced the previous installation-cache workaround with a unified, reliable preflight step run before update checks and updater startup.
  • Tests

    • Added tests and test helpers to verify quarantine removal and cache cleanup behavior.

@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 13, 2026 1:25am
cmux-staging Building Building Preview, Comment Jun 13, 2026 1:25am

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds SparkleUpdatePreflight to remove com.apple.quarantine attributes from bundled Sparkle.framework and to prepare/remove Sparkle’s installation cache; integrates the preflight into UpdateController and adds filesystem tests and test helpers.

Changes

Sparkle Preflight and Integration

Layer / File(s) Summary
Sparkle preflight struct and quarantine/cache logic
Packages/CmuxUpdater/Sources/CmuxUpdater/SparkleUpdatePreflight.swift
SparkleUpdatePreflight struct runs two operations: recursively removes com.apple.quarantine extended attributes from all items in bundled Sparkle.framework via removexattr, and prepares the installation cache by deriving its path from the host bundle identifier, detecting stale or invalid cache directories (non-directory files or empty directories), and removing them via FileManager.removeItem. Both operations log removal counts and failures.
UpdateController preflight wiring
Packages/CmuxUpdater/Sources/CmuxUpdater/UpdateController.swift
Replaces ensureSparkleInstallationCache() with runSparkleUpdatePreflight(), which instantiates SparkleUpdatePreflight with the host bundle, file manager, and logger, then calls run(). The preflight is invoked at updater lifecycle entry points and parameter docs updated to describe installer preflight.
Preflight tests, fixtures, and helpers
Packages/CmuxUpdater/Tests/CmuxUpdaterTests/SparkleUpdatePreflightTests.swift, Packages/CmuxUpdater/Tests/CmuxUpdaterTests/CapturingUpdateLog.swift, Packages/CmuxUpdater/Tests/CmuxUpdaterTests/ExtendedAttributeTestSupport.swift, Packages/CmuxUpdater/Tests/CmuxUpdaterTests/PreflightFixture.swift
Adds two filesystem-based tests: one that sets com.apple.quarantine on a bundled helper and verifies removal and log capture, and one that creates an empty Sparkle cache Installation directory and asserts it is removed. Supporting infrastructure includes CapturingUpdateLog, setExtendedAttribute/hasExtendedAttribute helpers, and PreflightFixture that creates a temporary app bundle and exposes Sparkle framework/cache URLs.

Sequence Diagram

sequenceDiagram
  participant UpdateController
  participant SparkleUpdatePreflight
  participant FileManager
  participant Syscalls
  UpdateController->>SparkleUpdatePreflight: run()
  SparkleUpdatePreflight->>FileManager: find Sparkle.framework in bundle
  SparkleUpdatePreflight->>FileManager: enumerator(at:) -> framework items
  loop per item
    SparkleUpdatePreflight->>Syscalls: removexattr(com.apple.quarantine)
  end
  SparkleUpdatePreflight->>FileManager: derive cache path, inspect directory
  SparkleUpdatePreflight->>FileManager: removeItem(at:) if invalid or empty
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • manaflow-ai/cmux#5132: Prior changes to UpdateController's Sparkle installation-cache preflight logic; closely related to this preflight refactor.

Poem

🐰 I hop through bundles, soft and light,
I scrub quarantine away at night.
I clear the cache, make installs clean,
A tiny rabbit on update green.
Hooray — the updater leaps, serene!


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (3 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Expensive Synchronous Load ❌ Error SparkleUpdatePreflight.run() recursively enumerates Sparkle.framework and calls removexattr; it’s invoked synchronously from @MainActor UpdateController.performCheckForUpdates/startUpdaterIfNeeded. Run the quarantine/cache preflight off the main actor (e.g., Task.detached/async background), and keep any recursive directory scan + per-file xattr removals off interactive/user-triggered main-actor paths.
Cmux Swift Logging ❌ Error SparkleUpdatePreflight appends production log lines containing user-specific filesystem paths via \(url.path) / \(installationURL.path) in error/success cases. Avoid logging full user-specific paths (e.g., omit url.path/installationURL.path or log only lastPathComponent/redacted values) while keeping counts/reasons; sanitize errors to exclude paths.
Cmux User-Facing Error Privacy ❌ Error New SparkleUpdatePreflight adds log messages with “Sparkle”/org.sparkle-project.Sparkle; UpdateLogStore.snapshot is copied via AppDelegate.copyUpdateLogs, making upstream vendor names user-visible. Redact/sanitize upstream vendor identifiers in preflight log messages (or when copying/surfacing update logs) so user-visible command/clipboard output contains no “Sparkle”/provider-specific strings.
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (17 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Repair Sparkle updater preflight' is concise and directly summarizes the main change: introducing and refactoring the SparkleUpdatePreflight step to fix Sparkle update issues.
Linked Issues check ✅ Passed The PR addresses issue #5123's root cause by removing stale quarantine attributes and orphaned cache directories from Sparkle components, though the XPC service removal referenced in the issue is implemented elsewhere, not in this PR.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the SparkleUpdatePreflight mechanism and supporting tests; no unrelated modifications are present.
Cmux Swift Actor Isolation ✅ Passed Production changes add SparkleUpdatePreflight and wire it into @MainActor UpdateController; SparkleUpdatePreflight is called synchronously on main actor and contains no @unchecked Sendable/shared-m...
Cmux Swift Blocking Runtime ✅ Passed SparkleUpdatePreflight.swift uses only removexattr/FileManager ops—no sleeps, semaphores, waits, polling, or manual locks. UpdateController’s preflight integration adds no new synchronization primi...
Cmux Cache Substitution Correctness ✅ Passed PR logic deletes stale Sparkle Installation cache by fresh filesystem checks (directory vs file, empty-contents) rather than substituting cached/opportunistic persisted state in persistence/history...
Cmux No Hacky Sleeps ✅ Passed PR #6004 changes are limited to .swift files; no .sh/.ts/.js/build runtime scripts and no sleep/timer/poll/wait tokens were found, so no hacky sleeps were introduced.
Cmux Algorithmic Complexity ✅ Passed SparkleUpdatePreflight does only one recursive file enumeration over Sparkle.framework and one contentsOfDirectory/emptiness check for Installation; no nested per-item rescans or repeated sorting/f...
Cmux Swift Concurrency ✅ Passed Scanned the PR Swift files for legacy async patterns (DispatchQueue/DispatchGroup/completion handlers/ungrounded fire-and-forget Tasks/combine sinks); none found—preflight is synchronous filesystem...
Cmux Swift @Concurrent ✅ Passed PR #6004 Swift diff adds synchronous SparkleUpdatePreflight + test/UpdateController wiring; searches found no @concurrent, nonisolated, async func, or await in changed Swift code.
Cmux Swift File And Package Boundaries ✅ Passed SparkleUpdatePreflight.swift is new and only 90 lines; no oversized/new mixed-responsibility file, and related logic lives in the SwiftPM CmuxUpdater target (not the app’s root Sources).
Cmux Full Internationalization ✅ Passed SparkleUpdatePreflight.swift adds only diagnostic log.append literals; UpdateController uses String(localized:) for user error (“update.error.notReady”), which exists in Resources/Localizable.xcstr...
Cmux Swiftui State Layout ✅ Passed PR #6004 only changes Sparkle preflight logic and related tests; no SwiftUI views/state patterns (ObservableObject/@Published/GeometryReader/List) appear in the diff.
Cmux Architecture Rethink ✅ Passed SparkleUpdatePreflight.swift does only synchronous xattr cleanup + cache deletion (enumerator/removexattr/removeItem); no sleeps/polling/observers/locks. Locks exist only in test CapturingUpdateLog...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR #6004 only updates Sparkle updater preflight/cache + tests; the 6 modified Swift files contain no NSWindow/NSPanel/NSWindowController/WindowGroup/SwiftUI window identifiers, so no aux-window sho...
Cmux Source Artifacts ✅ Passed No changed paths matched forbidden scratch/caches/DerivedData/logs/recordings dirs; CmuxUpdater changes are Swift source/tests plus Package.resolved, not local artifacts.
Description check ✅ Passed The pull request description includes all required template sections with sufficient detail: Summary (clearly explains what changed and why), Testing (includes specific test commands and scripts), and Checklist items implicitly covered through issue closure reference.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-5123-sparkle-cache-install

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the old ensureSparkleInstallationCache() workaround with a unified SparkleUpdatePreflight struct that strips com.apple.quarantine extended attributes from the bundled Sparkle.framework tree and prunes stale Installation cache entries before each update check. The new preflight is invoked from three call sites in UpdateController and is covered by two new regression tests.

  • New SparkleUpdatePreflight struct enumerates all files under Sparkle.framework calling removexattr for each, and conditionally removes the Sparkle Installation cache directory when it is a plain file or an empty directory.
  • UpdateController replaces all three ensureSparkleInstallationCache() call sites with runSparkleUpdatePreflight(), which creates a fresh SparkleUpdatePreflight and calls run() synchronously.
  • New test helpers (ExtendedAttributeTestSupport, PreflightFixture, CapturingUpdateLog) and a SparkleUpdatePreflightTests suite cover quarantine removal and empty-Installation-dir cleanup.

Confidence Score: 4/5

The preflight logic is correct and regression tests cover the intended scenarios, but the full Sparkle.framework enumeration (50–200 removexattr syscalls) runs synchronously on every update check from the @MainActor-isolated UpdateController.

The new SparkleUpdatePreflight struct introduces a per-check filesystem enumeration loop over Sparkle.framework that blocks the calling thread. All three call sites in UpdateController are @MainActor-isolated, so this loop runs on the main actor before every update check. On the first call to performCheckForUpdates or checkForUpdatesWhenReady, the loop also runs twice — once inside startUpdaterIfNeeded and once at the explicit call site — doubling the blocking work.

SparkleUpdatePreflight.swift and the three call sites in UpdateController.swift warrant a second look to ensure the framework enumeration is dispatched off the main actor before this merges.

Important Files Changed

Filename Overview
Packages/CmuxUpdater/Sources/CmuxUpdater/SparkleUpdatePreflight.swift New struct performing filesystem work (full Sparkle.framework directory enumeration + per-file removexattr syscalls) called synchronously from @mainactor call sites in UpdateController on every update check.
Packages/CmuxUpdater/Sources/CmuxUpdater/UpdateController.swift Replaces three ensureSparkleInstallationCache() call sites with runSparkleUpdatePreflight(); the new helper is more expensive per call than its predecessor since it includes a full framework tree enumeration in addition to the cache check.
Packages/CmuxUpdater/Tests/CmuxUpdaterTests/SparkleUpdatePreflightTests.swift Two regression tests covering quarantine removal and empty Installation directory cleanup; test fixture writes into the real ~/Library/Caches with no teardown for parent directories.
Packages/CmuxUpdater/Tests/CmuxUpdaterTests/PreflightFixture.swift Test fixture creates a fake bundle in /tmp and uses a UUID-keyed path in the real ~/Library/Caches; rootURL and sparkleCacheURL parent directories are never cleaned up by a teardown block.
Packages/CmuxUpdater/Tests/CmuxUpdaterTests/ExtendedAttributeTestSupport.swift Small test-only helpers wrapping setxattr/getxattr for fixture setup and verification; straightforward and correct.
Packages/CmuxUpdater/Tests/CmuxUpdaterTests/CapturingUpdateLog.swift Thread-safe test double for UpdateLogging backed by NSLock; correct and minimal.

Reviews (3): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment on lines +385 to 391
private func runSparkleUpdatePreflight() {
SparkleUpdatePreflight(
hostBundle: hostBundle,
fileManager: fileManager,
log: log
).run()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Synchronous filesystem loop on @MainActor

UpdateController is @MainActor-isolated, so runSparkleUpdatePreflight() runs synchronously on the main thread. On a fresh quarantined install, SparkleUpdatePreflight.run() enumerates every file in Sparkle.framework (typically 50–200 items) and calls removexattr for each — all blocking syscalls on the main actor — before returning. This violates the cmux rule against expensive synchronous disk/syscall loads on the main actor or interactive paths. The call originates from user-triggered paths (startUpdaterIfNeeded, checkForUpdatesWhenReady, performCheckForUpdates), where a stall would be visible.

The fix is to dispatch the preflight off the main actor, e.g. via Task.detached(priority: .utility), or by making SparkleUpdatePreflight an async function that hops to a non-isolated executor before doing the syscall loop.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +43 to +49
let result = removexattr(url.path, "com.apple.quarantine", 0)
if result == 0 {
return true
}
if errno != ENOATTR {
log.append("Failed removing Sparkle quarantine attribute at \(url.path): errno \(errno)")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 url.path bridges to a C const char * via Swift's UTF-8 string encoding. For POSIX/Darwin syscalls that accept a filesystem path, the canonical spelling is (url as NSURL).fileSystemRepresentation, which uses the OS-level file-system representation and handles edge cases (non-ASCII bundle paths in unusual install locations) more robustly than the Swift String bridge.

Suggested change
let result = removexattr(url.path, "com.apple.quarantine", 0)
if result == 0 {
return true
}
if errno != ENOATTR {
log.append("Failed removing Sparkle quarantine attribute at \(url.path): errno \(errno)")
}
let result = (url as NSURL).withUnsafeFileSystemRepresentation { path -> Int32 in
guard let path else { return -1 }
return removexattr(path, "com.apple.quarantine", 0)
}
if result == 0 {
return true
}
if errno != ENOATTR {
log.append("Failed removing Sparkle quarantine attribute at \(url.path): errno \(errno)")
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +40 to +52

#expect(!FileManager.default.fileExists(atPath: installationURL.path))
}
}

private final class CapturingUpdateLog: UpdateLogging, @unchecked Sendable {
private let lock = NSLock()
private(set) var messages: [String] = []

func append(_ message: String) {
lock.withLock {
messages.append(message)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test fixture leaks real filesystem state on failure

sparkleCacheURL writes into the user's real ~/Library/Caches directory. The Installation directory is only removed if the preflight under test runs successfully. If the #expect or any prior assertion fails before the preflight call, the directory leaks. Similarly, rootURL in the tmp directory is never deleted by a teardown block or defer. Adding addTeardownBlock (or a defer in PreflightFixture.init) that removes rootURL and sparkleCacheURL would make the fixture self-cleaning on any exit path.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Packages/CmuxUpdater/Sources/CmuxUpdater/UpdateController.swift`:
- Around line 221-223: Both checkForUpdatesWhenReady() and
performCheckForUpdates() call runSparkleUpdatePreflight(), causing the preflight
to run twice; remove the duplicate by keeping a single invocation per flow —
e.g., remove the runSparkleUpdatePreflight() call from performCheckForUpdates()
and ensure checkForUpdatesWhenReady() performs the synchronous preflight once
before delegating to performCheckForUpdates(); apply the same deduplication for
the alternate path around the code near the other occurrence (the block around
lines 248-256) so runSparkleUpdatePreflight() is only called once per check
flow.

In
`@Packages/CmuxUpdater/Tests/CmuxUpdaterTests/SparkleUpdatePreflightTests.swift`:
- Around line 29-42: Add a test covering the branch where the "Installation"
path exists as a regular file rather than a directory: in
SparkleUpdatePreflightTests create a file at
fixture.sparkleCacheURL.appendingPathComponent("Installation", isDirectory:
true) (use FileManager.default.createFile or write Data) instead of creating a
directory, call SparkleUpdatePreflight(hostBundle: fixture.bundle, fileManager:
.default, log: CapturingUpdateLog()).run(), and assert the file is removed
afterwards (use FileManager.default.fileExists(atPath:) or similar) to validate
SparkleUpdatePreflight.run() handles the non-directory Installation path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e590ad79-8004-46f4-b227-8a1372430710

📥 Commits

Reviewing files that changed from the base of the PR and between aeb8847 and 7477a54.

📒 Files selected for processing (3)
  • Packages/CmuxUpdater/Sources/CmuxUpdater/SparkleUpdatePreflight.swift
  • Packages/CmuxUpdater/Sources/CmuxUpdater/UpdateController.swift
  • Packages/CmuxUpdater/Tests/CmuxUpdaterTests/SparkleUpdatePreflightTests.swift

Comment on lines 221 to +223
private func performCheckForUpdates() {
startUpdaterIfNeeded()
ensureSparkleInstallationCache()
runSparkleUpdatePreflight()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Avoid duplicate preflight execution in a single update-check flow.

checkForUpdatesWhenReady() and performCheckForUpdates() both call runSparkleUpdatePreflight(), so the canCheck == true path performs the same synchronous filesystem preflight twice for one action. Keep a single invocation per check flow.

Suggested minimal fix
 private func checkForUpdatesWhenReady() {
     cancelReadinessRetry()
     startUpdaterIfNeeded()
-    runSparkleUpdatePreflight()
     let canCheck = updater.canCheckForUpdates
     log.append("checkForUpdatesWhenReady invoked (canCheck=\(canCheck))")
     if canCheck {
         performCheckForUpdates()
         return

Also applies to: 248-256

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Packages/CmuxUpdater/Sources/CmuxUpdater/UpdateController.swift` around lines
221 - 223, Both checkForUpdatesWhenReady() and performCheckForUpdates() call
runSparkleUpdatePreflight(), causing the preflight to run twice; remove the
duplicate by keeping a single invocation per flow — e.g., remove the
runSparkleUpdatePreflight() call from performCheckForUpdates() and ensure
checkForUpdatesWhenReady() performs the synchronous preflight once before
delegating to performCheckForUpdates(); apply the same deduplication for the
alternate path around the code near the other occurrence (the block around lines
248-256) so runSparkleUpdatePreflight() is only called once per check flow.

Comment on lines +29 to +42
@Test func removesEmptyInstallationDirectoryBeforeSparkleCreatesInstallSession() throws {
let fixture = try PreflightFixture()
let installationURL = fixture.sparkleCacheURL
.appendingPathComponent("Installation", isDirectory: true)
try FileManager.default.createDirectory(at: installationURL, withIntermediateDirectories: true)

SparkleUpdatePreflight(
hostBundle: fixture.bundle,
fileManager: .default,
log: CapturingUpdateLog()
).run()

#expect(!FileManager.default.fileExists(atPath: installationURL.path))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the stale-file Installation path branch.

The suite validates empty-directory cleanup, but it doesn’t cover the Installation path existing as a regular file (!isDirectory branch). Add one test that creates a file at that path and asserts preflight removes it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/CmuxUpdater/Tests/CmuxUpdaterTests/SparkleUpdatePreflightTests.swift`
around lines 29 - 42, Add a test covering the branch where the "Installation"
path exists as a regular file rather than a directory: in
SparkleUpdatePreflightTests create a file at
fixture.sparkleCacheURL.appendingPathComponent("Installation", isDirectory:
true) (use FileManager.default.createFile or write Data) instead of creating a
directory, call SparkleUpdatePreflight(hostBundle: fixture.bundle, fileManager:
.default, log: CapturingUpdateLog()).run(), and assert the file is removed
afterwards (use FileManager.default.fileExists(atPath:) or similar) to validate
SparkleUpdatePreflight.run() handles the non-directory Installation path.

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.

Sparkle auto-update fails on macOS (non-sandboxed): SUSparkleErrorDomain(4005) / Timeout: agent connection was never initiated

1 participant