Skip to content

fix: repair NIGHTLY Sparkle quarantine metadata - #1703

Merged
austinywang merged 4 commits into
mainfrom
fix/1699-nightly-quarantine
Mar 18, 2026
Merged

austinywang merged 4 commits into
mainfrom
fix/1699-nightly-quarantine

Conversation

@austinywang

@austinywang austinywang commented Mar 18, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1699.

Regression

  • The recent user-visible regression was triggered by e15825826f36ea007c0262f375f5585888dc4e21 on March 17, 2026, which restored automatic Sparkle update checks for NIGHTLY.
  • The nightly packaging workflow is not the source of the malformed com.apple.quarantine value. That attribute is created on the client during Sparkle download/extract, so the fix is in the updater integration.

What changed

  • Add a regression test commit first so CI can prove the malformed quarantine case is covered.
  • Repair Sparkle download and extracted-app quarantine metadata when LaunchServices leaves the source app identifier blank (0383;...;;).
  • Re-write the quarantine properties with the current app bundle identifier and app name so Gatekeeper no longer shows (null) for the creating app.

Verification

  • ./scripts/reload.sh --tag fix-1699-quarantine
  • xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -configuration Debug -destination 'platform=macOS' -derivedDataPath /tmp/cmux-fix-1699-quarantine-tests build-for-testing\n- Tests were not executed locally per repo policy; only the app build and test build artifacts were verified.

Summary by cubic

Fixes a NIGHTLY regression where Sparkle wrote malformed quarantine metadata that made Gatekeeper show (null) as the source app. We now repair com.apple.quarantine on the downloaded archive and the extracted app during the update.

  • Bug Fixes
    • Repair quarantine metadata on willExtract (downloaded archive) and during extraction (extracted app): set agent bundle ID, agent name, type, and data URL when LaunchServices leaves them blank.
    • Locate and target the newest relevant archive/app in Sparkle caches; skip when not found or already valid.
    • Add concise logging (stage, path, before/after raw values) and regression tests to confirm and trace repairs.

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

Summary by CodeRabbit

  • New Features
    • Updates now detect and repair missing quarantine/Launch Services metadata for downloaded and extracted updates, improving installation reliability.
  • Bug Fixes
    • Quarantine repair is triggered during extraction so updates are usable immediately after install; failures are logged and handled gracefully.
  • Tests
    • Added test coverage for quarantine repair behavior and discovering the newest downloaded/extracted update items.

@vercel

vercel Bot commented Mar 18, 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 Mar 18, 2026 8:20am

@greptile-apps greptile-apps Bot left a comment

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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented Mar 18, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6fad648d-5869-42ba-8eac-2525afbba9ac

📥 Commits

Reviewing files that changed from the base of the PR and between d7cd288 and aa6dda8.

📒 Files selected for processing (1)
  • GhosttyTabs.xcodeproj/project.pbxproj

📝 Walkthrough

Walkthrough

Adds a new UpdateQuarantineRepair utility to discover and repair com.apple.quarantine metadata on downloaded archives and extracted apps; integrates repair calls into the Sparkle updater delegate and UpdateDriver flow; adds tests and updates the Xcode project to include the new source and test files.

Changes

Cohort / File(s) Summary
Project Configuration
GhosttyTabs.xcodeproj/project.pbxproj
Adds file references and build entries for Sources/Update/UpdateQuarantineRepair.swift and cmuxTests/UpdateQuarantineRepairTests.swift; minor indentation/formatting updates.
Updater Delegate & Driver
Sources/Update/UpdateDelegate.swift, Sources/Update/UpdateDriver.swift
Wires quarantine-repair hooks into Sparkle lifecycle: adds updater(_:willExtractUpdate:) delegate call, logging helper, state flags, a prepare method, and calls to attempt repair for downloaded archives and extracted apps during extraction progress/start.
Quarantine Repair Utility
Sources/Update/UpdateQuarantineRepair.swift
New utility that locates downloaded archives and extracted apps, reads/writes com.apple.quarantine xattr, infers Launch Services metadata (agent bundle/id/name, type, data URL), performs conditional repairs, and returns structured results (outcome + before/after raw values).
Tests
cmuxTests/UpdateQuarantineRepairTests.swift
New XCTestCase covering repair behavior (adding LS metadata), idempotence, locating newest matching archive/app, and helper utilities for temp files, timestamps, and writing quarantine xattrs.

Sequence Diagram

sequenceDiagram
    participant SPU as SPUUpdater
    participant UDel as UpdateDriver<br/>(Delegate)
    participant QR as UpdateQuarantineRepair
    participant FS as File System<br/>(xattr)

    rect rgba(100,150,200,0.5)
    Note over SPU,FS: Pre-extract / downloaded archive repair
    SPU->>UDel: willExtractUpdate(item)
    UDel->>QR: repairDownloadedArchiveIfNeeded(host, version, ...)
    QR->>FS: locate archive in Sparkle cache
    QR->>FS: read `com.apple.quarantine` xattr
    QR->>QR: evaluate & possibly modify quarantine fields
    QR->>FS: write updated xattr
    QR-->>UDel: UpdateQuarantineRepairResult
    UDel->>UDel: logUpdateQuarantineRepair(...)
    end

    rect rgba(150,100,200,0.5)
    Note over SPU,FS: Extraction / extracted app repair
    SPU->>UDel: extraction progress / start
    UDel->>QR: repairExtractedApplicationIfNeeded(...)
    QR->>FS: locate extracted app in Installation path
    QR->>FS: read `com.apple.quarantine` xattr
    QR->>QR: evaluate & possibly modify quarantine fields (LS metadata)
    QR->>FS: write updated xattr
    QR-->>UDel: UpdateQuarantineRepairResult
    UDel->>UDel: logUpdateQuarantineRepair(...)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
I hopped through caches late at night,
I stitched the xattrs till they read right,
gave Agent IDs and data URLs a nudge,
now Gatekeeper smiles — no launch-time grudge,
Hop! The apps release and skip the judge.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers regression context, what changed, and verification steps, but lacks structured sections matching the template and omits testing details. Reorganize description using template sections (Summary, Testing, Checklist) and clarify test execution status, testing approach, and bot review requests.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main objective: repairing NIGHTLY Sparkle quarantine metadata to fix the (null) creator issue.
Linked Issues check ✅ Passed Code changes comprehensively address issue #1699 objectives: repair quarantine metadata, locate newest archives/apps, handle LaunchServices blank fields, and add regression tests.
Out of Scope Changes check ✅ Passed All code changes are directly related to fixing quarantine metadata repair; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1699-nightly-quarantine
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 4 files

@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.

🧹 Nitpick comments (1)
cmuxTests/UpdateQuarantineRepairTests.swift (1)

137-172: Consider adding test cleanup to avoid temp file accumulation.

The test helpers create temporary files and directories but don't clean them up. While acceptable, consider adding addTeardownBlock or overriding tearDown() to remove the created temp directories to avoid accumulating test artifacts over time.

♻️ Optional cleanup implementation
final class UpdateQuarantineRepairTests: XCTestCase {
    private var tempDirectories: [URL] = []
    
    override func tearDown() {
        super.tearDown()
        for url in tempDirectories {
            try? FileManager.default.removeItem(at: url)
        }
        tempDirectories.removeAll()
    }
    
    private func makeTemporaryDirectory(named name: String) throws -> URL {
        let directoryURL = FileManager.default.temporaryDirectory
            .appendingPathComponent("UpdateQuarantineRepairTests", isDirectory: true)
            .appendingPathComponent(UUID().uuidString, isDirectory: true)
            .appendingPathComponent(name, isDirectory: true)
        try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true)
        tempDirectories.append(directoryURL.deletingLastPathComponent().deletingLastPathComponent())
        return directoryURL
    }
    // ... rest unchanged
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/UpdateQuarantineRepairTests.swift` around lines 137 - 172, Test
helpers (makeTemporaryDirectory, makeTemporaryFile, createFile) create temp
dirs/files but never remove them; add cleanup by tracking created root temp
directories (e.g., a private var tempDirectories: [URL]) and implement
tearDown() to iterate and remove each URL with
FileManager.default.removeItem(at:) (or register addTeardownBlock when creating
each directory) so makeTemporaryDirectory appends the created root URL to
tempDirectories and tearDown removes and clears the list.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cmuxTests/UpdateQuarantineRepairTests.swift`:
- Around line 137-172: Test helpers (makeTemporaryDirectory, makeTemporaryFile,
createFile) create temp dirs/files but never remove them; add cleanup by
tracking created root temp directories (e.g., a private var tempDirectories:
[URL]) and implement tearDown() to iterate and remove each URL with
FileManager.default.removeItem(at:) (or register addTeardownBlock when creating
each directory) so makeTemporaryDirectory appends the created root URL to
tempDirectories and tearDown removes and clears the list.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 84cdc53a-6810-4e29-a1aa-600f487b6fbb

📥 Commits

Reviewing files that changed from the base of the PR and between 55cb5c6 and a72b2a2.

📒 Files selected for processing (4)
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Sources/Update/UpdateDelegate.swift
  • Sources/Update/UpdateQuarantineRepair.swift
  • cmuxTests/UpdateQuarantineRepairTests.swift

…699-nightly-quarantine

# Conflicts:
#	GhosttyTabs.xcodeproj/project.pbxproj

@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.

🧹 Nitpick comments (2)
Sources/Update/UpdateDriver.swift (1)

322-325: Avoid persisting raw quarantine blobs in logs.

beforeRawValue / afterRawValue can carry source metadata (including URLs). Prefer redacted or structured fields to reduce sensitive-data exposure in persistent logs.

🔒 Suggested redaction
- let before = result.beforeRawValue ?? "<none>"
- let after = result.afterRawValue ?? "<none>"
- UpdateLogStore.shared.append("quarantine repair extracted-app: \(result.outcome) path=\(path) before=\(before) after=\(after)")
+ UpdateLogStore.shared.append("quarantine repair extracted-app: \(result.outcome) path=\(path)")
Sources/Update/UpdateDelegate.swift (1)

128-133: Consider centralizing quarantine-repair log formatting.

This formatter duplicates logic already present in Sources/Update/UpdateDriver.swift (extracted-app logging). A shared formatter/helper will prevent divergence and make future redaction updates one-place.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Update/UpdateDelegate.swift` around lines 128 - 133, The
quarantine-repair log formatting in UpdateDelegate.logUpdateQuarantineRepair
duplicates logic used in UpdateDriver (extracted-app logging); extract the
formatting into a single shared helper (e.g., a new
UpdateLogFormatter.formatQuarantineRepair(stage:result:) or similar) that takes
the stage string and an UpdateQuarantineRepairResult and returns the fully
formatted log message (handling url/path, beforeRawValue, afterRawValue and
redaction rules), then update UpdateDelegate.logUpdateQuarantineRepair to call
that helper and likewise switch the duplicated code in UpdateDriver to use the
same helper so formatting lives in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Sources/Update/UpdateDelegate.swift`:
- Around line 128-133: The quarantine-repair log formatting in
UpdateDelegate.logUpdateQuarantineRepair duplicates logic used in UpdateDriver
(extracted-app logging); extract the formatting into a single shared helper
(e.g., a new UpdateLogFormatter.formatQuarantineRepair(stage:result:) or
similar) that takes the stage string and an UpdateQuarantineRepairResult and
returns the fully formatted log message (handling url/path, beforeRawValue,
afterRawValue and redaction rules), then update
UpdateDelegate.logUpdateQuarantineRepair to call that helper and likewise switch
the duplicated code in UpdateDriver to use the same helper so formatting lives
in one place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7b9305e-66ee-46e3-95dc-ed4d2138908d

📥 Commits

Reviewing files that changed from the base of the PR and between a72b2a2 and d7cd288.

📒 Files selected for processing (2)
  • Sources/Update/UpdateDelegate.swift
  • Sources/Update/UpdateDriver.swift

@austinywang
austinywang merged commit 629b63d into main Mar 18, 2026
10 of 14 checks passed
@austinywang
austinywang deleted the fix/1699-nightly-quarantine branch March 18, 2026 08:23
austinywang added a commit that referenced this pull request Mar 18, 2026
austinywang added a commit that referenced this pull request Mar 18, 2026
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
* test: add quarantine regression coverage

* fix: repair Sparkle quarantine metadata for nightly updates

* fix: repair extracted Sparkle app on extraction callbacks
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
israeligal pushed a commit to israeligal/cmux that referenced this pull request May 2, 2026
rodchristiansen pushed a commit to rodchristiansen/cmux that referenced this pull request Sep 2, 2026

This branch was successfully deployed

1 active deployment
Preview — aa6dda83 Deployed Mar 18, 2026 by vercel[bot]
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.

cmux NIGHTLY can't be opened — macOS says "created by the app (null)"

1 participant