Skip to content

Fetch bootstrap data uncached so manifest hash diffs always self-heal#2

Merged
rodchristiansen merged 2 commits into
mainfrom
fix/always-fresh-manifest
Jun 16, 2026
Merged

Fetch bootstrap data uncached so manifest hash diffs always self-heal#2
rodchristiansen merged 2 commits into
mainfrom
fix/always-fresh-manifest

Conversation

@rodchristiansen

Copy link
Copy Markdown
Contributor

Problem

A re-run of BootstrapMate on an already-provisioned Mac kept executing an old cached ProvisioningWatcher.sh even though management.json had been updated with a new hash for it. The log showed:

Already have valid file: /Library/Managed Bootstrap/cache/ProvisioningWatcher.sh. Skipping re-download.

Root cause

NetworkManager builds every request with URLRequest(url:) (default .useProtocolCachePolicy) on a URLSession(configuration: .default) that uses the shared URLCache. So management.json itself can be served stale — from the device's URL cache or a CDN edge that hasn't purged yet.

ManifestManager.downloadIfNeeded is correct in isolation — it compares the on-disk file's SHA-256 to item.hash from the manifest — but if the manifest is stale, expectedHash is stale, so the old cached payload matches and re-download is skipped. The hash check can only self-heal if the manifest it trusts is fresh.

Fix

Route all fetches through a shared no-cache session:

config.urlCache = nil
config.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData

Now management.json (and every pkg/script) reflects origin truth on each run, so a changed hash always triggers a re-download — the hash diff self-heals without waiting on CDN purge propagation. Bootstrap runs are infrequent, so bypassing caches has negligible cost.

Test

swift build succeeds; change is isolated to Managers/NetworkManager.swift.

NetworkManager built requests with the default cache policy and the shared
URLCache, so management.json could be served stale — from the device URL cache
or a CDN edge that hadn't purged yet. ManifestManager.downloadIfNeeded then
compared cached files against that stale expected hash, matched, and logged
'Already have valid file. Skipping re-download' — so an updated payload such as
ProvisioningWatcher.sh kept running the old cached copy across BootstrapMate runs.

Use a shared no-cache session (urlCache = nil, requestCachePolicy =
reloadIgnoringLocalAndRemoteCacheData) for every fetch so the manifest, and thus
each per-item hash comparison, always reflects origin truth and a changed hash
self-heals on the next run.

Copilot AI 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.

Pull request overview

This PR updates the core networking layer to bypass URL caching so that management.json and related bootstrap artifacts are fetched fresh on each run, preventing stale manifests from causing hash validation to incorrectly skip re-downloads.

Changes:

  • Introduces a shared URLSession configured with urlCache = nil and .reloadIgnoringLocalAndRemoteCacheData.
  • Updates downloadData and downloadFile to use the no-cache session (and explicit per-request cache policy).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +15 to +20
/// Session that never serves cached responses. Bootstrap data must reflect ORIGIN
/// truth on every run: management.json drives the per-item hash check, so a stale
/// manifest (from the local URL cache or a CDN edge that hasn't purged yet) makes a
/// changed file — e.g. ProvisioningWatcher.sh — compare against a stale expected hash,
/// get judged "already valid", and never re-download. Disabling the URL cache and
/// ignoring local + remote caches makes the hash diff always self-heal.
Comment on lines 28 to +34
public func downloadData(
from url: URL,
followRedirects: Bool,
authHeader: String?,
completion: @escaping @Sendable (Data?, Error?) -> Void
) {
var request = URLRequest(url: url)
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData)
…fest

# Conflicts:
#	Sources/core/Managers/NetworkManager.swift
@rodchristiansen rodchristiansen merged commit 98a9fd2 into main Jun 16, 2026
@rodchristiansen rodchristiansen deleted the fix/always-fresh-manifest branch June 16, 2026 01:58
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.

2 participants