Skip to content

Fix skipIf inversion, unchecked download status, and dropped preflight scripts - #27

Merged
rodchristiansen merged 1 commit into
mainfrom
fix/manifest-skip-and-download-status
Sep 5, 2026
Merged

Fix skipIf inversion, unchecked download status, and dropped preflight scripts#27
rodchristiansen merged 1 commit into
mainfrom
fix/manifest-skip-and-download-status

Conversation

@rodchristiansen

Copy link
Copy Markdown
Contributor

Three bugs in the manifest/download path, which is why they are one PR.

skipIf was evaluated twice with opposite senses (#13)

IAOrchestrator.shouldSkipForArchitecture used the InstallApplications sense — skip when the named architecture is the current one — while ManifestManager.shouldSkip, which gated the download, returned the opposite. So on the architecture an item was written for, the stage loop correctly ran it while downloadIfNeeded decided it was skipped, logged Skipping … due to skip_if: and returned success without fetching. The install then ran against a missing file and the log blamed a skip rather than a failure.

Both private copies are deleted and replaced by one helper, ArchitectureSkip in Sources/core/Utilities/, keeping the orchestrator's sense. It takes the current architecture as a defaulted parameter so the logic is testable without a real machine.

The download gate in downloadIfNeeded is kept rather than removed: it is now consistent with the stage loops, and it is the only skip check protecting call sites that do not do their own.

Download response status was never inspected (#21)

downloadFile discarded the URLResponse, so a 404 page, a CDN error document or an auth challenge body was written to the destination as if it were the payload — surfacing one step later as a hash mismatch, or not at all for an item with no hash. Any non-2xx is now a failed download, logging the status code and the URL; 401 and 403 say explicitly that the origin is private and the request carried no valid Authorization header. DownloadError gained a LocalizedError conformance so that detail actually reaches the log instead of a generic Foundation description.

Only the first preflight rootscript ran (#22)

runPreflightStage used items.first(where: { $0.type == "rootscript" }) with no loop and no warning, so a preflight of four scripts silently ran one.

Decision: loop, rather than keep it single-script. The stage now iterates in manifest order like setupassistant and userland, and the existing short-circuits are preserved per item — exit 0 still returns .skipBootstrap immediately, a download failure or a negative exit code still returns .failed immediately, and exit 1+ moves on to the next script. The status plist records the last exit code reached. Preflight is still rootscript-only (that is a spec constraint, not an accident), but a non-rootscript item is now named in a warning instead of dropped in silence, and per-item skipIf is honoured here the way it is in the other two stages — previously the preflight script went straight to downloadIfNeeded with no skip check of its own.

Tests

ArchitectureSkipTests pins the helper in both directions, case-insensitively, plus the unrecognized-value case. Added in the existing swift-testing style of the file.

Not compiled or tested locally

This was written on a machine with no Swift toolchain, so nothing here was built or run — it relies entirely on CI. Please treat the build and test results as the first check.

What a reviewer should look at first: the sense of ArchitectureSkip.shouldSkip (a second inversion here would be worse than the bug), and the preflight loop's short-circuit ordering against the old single-script flow.

https://claude.ai/code/session_01LQxgdJHaS4UhjJnJueqhcs

…t scripts

Three defects in the manifest and download path.

skipIf was interpreted twice with opposite senses. The stage loops used the
InstallApplications sense (skip when the named architecture is the current one)
while the download gate in ManifestManager used the inverse, so on the very
architecture an item was written for the payload was never fetched and the
download reported success. The install or script step then ran against a file
that was not there, and the log said "skipped" rather than "failed". Both call
sites now use one shared helper, ArchitectureSkip, which keeps the orchestrator
sense; the two private copies are gone.

The download response status was never inspected. Whatever a server returned —
a 404 page, a CDN error document, an auth challenge — was written to the
destination as though it were the payload, surfacing later as a hash mismatch
or, for an item with no hash, not at all. Any non-2xx now fails the download and
logs the status code and the URL, with 401 and 403 called out as the signature
of a private origin without a valid Authorization header. DownloadError also
conforms to LocalizedError so that detail reaches the log rather than a generic
Foundation description.

Only the first preflight rootscript ran. The stage picked items.first(where:)
with no loop and no warning, so a preflight of four scripts silently ran one and
nothing in the log distinguished that from a preflight of one. The stage now
iterates in manifest order like the other two, applying the existing
short-circuits per item: exit 0 still skips the rest of the bootstrap, a
download failure or a negative exit code still fails the stage, and exit 1+
moves on to the next script. Preflight remains rootscript-only, but a
non-rootscript item is now named in a warning instead of dropped in silence, and
per-item skipIf is honoured the way it is in the other stages.

Adds tests pinning the skipIf helper in both directions.

Fixes #13
Fixes #21
Fixes #22

Claude-Session: https://claude.ai/code/session_01LQxgdJHaS4UhjJnJueqhcs
@rodchristiansen
rodchristiansen merged commit d1d4567 into main Sep 5, 2026
2 checks passed
@rodchristiansen
rodchristiansen deleted the fix/manifest-skip-and-download-status branch September 5, 2026 17:06
rodchristiansen added a commit that referenced this pull request Sep 5, 2026
Both branches changed IAOrchestrator. #27 replaced the private
shouldSkipForArchitecture and getCurrentArchitecture helpers with the shared
ArchitectureSkip type; this branch rewrote waitForUserSession to be bounded
by the userlandLoginTimeout preference.

Resolved by keeping the bounded waitForUserSession and dropping both
architecture helpers, which the shared type supersedes.

Claude-Session: https://claude.ai/code/session_01LQxgdJHaS4UhjJnJueqhcs
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