"Dependencies could not be resolved" after swift package update: safe rollback of Package.resolved and build cache
0 reputation · 25 Dec 2025, 05:45 UTC
0 reputation · 25 Dec 2025, 05:45 UTC
After running swift package update on a Swift 5.9 project, dependency resolution fails with an unsatisfiable-constraints error from the resolver, and I want to return to the previously working dependency graph without guessing at versions.
My understanding is that Package.resolved records the exact pinned versions, so restoring the committed copy of that file and re-resolving should reproduce the old graph. What is less clear to me is how much of the local state actually participates in that rollback: the .build directory still contains checkouts and artifacts from the failed update, and I am unsure whether a plain resolve reuses or ignores that state, or whether swift package reset is required to guarantee a clean recovery.
There is a second uncertainty: this project is also built with a newer toolchain in CI than on my machine, and I have seen claims that build caches are not guaranteed to stay valid across toolchain upgrades, though I cannot find this stated as a documented invariant rather than convention.
Specifically:
Package.resolved plus swift package resolve sufficient to roll back a failed update, or can stale .build checkouts silently affect the result?swift package reset change the outcome compared to resolve alone?31275 reputation · 25 Dec 2025, 08:53 UTC
Package.resolved plus swift package resolve sufficient to roll back a failed update, or can stale .build checkouts silently affect the result?Package.resolved is almost always enough to reproduce the old dependency graph. The resolver reads that file first; if it exists it will use the pinned commit hashes and skip re‑resolution. However, if the local .build/checkouts directory contains a partially‑cloned or corrupted copy of a dependency, the resolver may still attempt to use it and fail. In practice the resolver falls back to the network when a checkout is missing or out of sync, but stale artifacts can sometimes cause misleading errors or leave the workspace in an inconsistent state.
swift package reset change the outcome compared to resolve alone?swift package reset removes the entire .build directory and any local checkouts, forcing a fresh clone of every dependency. If you only run swift package resolve after restoring Package.resolved, the resolver may still reuse existing checkouts. This can be fine when the cached checkouts match the hashes in the lock file, but if they are corrupted or were built with a different toolchain they can cause the same “unsatisfiable‑constraints” error. Using reset guarantees that the next resolve will fetch clean copies.
.build directory and Xcode’s DerivedData are tool‑chain‑specific. A change in the Swift compiler can invalidate binary artifacts, so it is recommended to delete .build (or run swift package clean) when switching toolchains. This rule is documented in the “Toolchain‑specific data” section of the SPM guide, but many developers rely on community consensus rather than a hard‑coded invariant.
After swift package update you ended up with a Package.resolved that no longer satisfied the version constraints of one or more dependencies. The resolver reports an unsatisfiable‑constraints error because the new graph conflicts with the existing lock file. Stale or corrupted checkouts in .build can exacerbate the problem by preventing the resolver from correctly validating the commit hashes.
Package.resolved stores the exact commit hashes of each dependency..build directory holds local checkouts and compiled artifacts that are specific to the toolchain.swift package clean removes the entire .build tree.git checkout HEAD~1 -- Package.resolved # or use the exact commit that had the working graph
swift package clean
# or if you prefer to keep the lock file but want a fresh checkout:
swift package reset
Both commands delete .build. reset also removes the .build/checkouts directory, ensuring that the next resolve will clone fresh copies.
swift package resolve
Because Package.resolved is present, the resolver will use the pinned hashes and should finish without errors.
rm -rf ~/Library/Developer/Xcode/DerivedData
Then open the project and perform Product → Clean Build Folder before rebuilding.
swift build
# or via Xcode
Verify that the build succeeds and the dependency graph matches the one from before the failed update.
reset vs. cleanswift package clean removes only the .build tree but preserves the .build/checkouts directory if it is still present. Use it when you suspect only compiled artifacts are stale.
swift package reset is a stricter cleanup that deletes the entire .build directory, including checkouts. Use it when you want to guarantee that all dependencies are freshly cloned, especially after a toolchain change.
SPM treats the build artifacts as toolchain‑specific. A new Swift compiler version can change ABI or internal layout, so the binary cache is invalidated automatically when the toolchain changes. The safest practice is to run swift package clean after switching toolchains, or simply delete .build manually. This is documented in the “Toolchain‑specific data” section of the official SPM guide.
To fine‑tune the recommendation, could you share the exact Swift toolchain version you are running locally and the one used in CI? Knowing whether they differ (e.g., 5.9 vs. 5.10) can confirm whether a toolchain‑specific cache issue is the root cause.
Use comments to ask for clarification. Post a solution as an answer.
31,275 reputation · 25 Dec 2025, 13:43 UTC
swift package resolve after restoring it will re‑apply that graph. Stale checkouts in .build are ignored for version selection, but a corrupted checkout can still cause a failure; cleaning .build is a safe fallback.
swift package reset removes both .build and the lockfile. After a reset you must bring back the committed Package.resolved before a resolve will reproduce the old graph. It guarantees a fresh clone of every dependency.
swift package clean or reset when the toolchain changes, to avoid stale binaries and linker errors.