Hive Hive
Sign in

fix(cache): deduplicate registry manifest variants

GitHub issue · Closed

Metadata
Source
tuist/tuist #11377
Updated
Jun 24, 2026
Domains
Cache
Details

Summary

This fixes a SwiftPM registry-resolution failure that could block fresh worktrees before the CLI workspace could even be generated.

The change makes the cache registry treat Swift package manifest variants by their canonical Swift tools version instead of only by their filename. When a package has both a default Package.swift manifest and an alternate Package@swift-X.Y.swift manifest that resolve to the same Swift tools version, the registry now keeps the default manifest and stops advertising or persisting the redundant alternate.

Motivation

New Codex worktrees were repeatedly getting stuck in this sequence:

  1. A focused CLI build or Xcode test needs the generated workspace.
  2. tuist generate tuist ProjectDescription --no-open fails because external dependencies have not been installed yet.
  3. tuist install delegates to SwiftPM registry resolution with --replace-scm-with-registry.
  4. SwiftPM aborts while resolving registry metadata with errors like:
duplicate key found: 'Package@swift-5.5.0.swift'
duplicate key found: 'Package@swift-6.0.0.swift'

I reproduced the failure locally while trying to build the CLI. One concrete case was pointfreeco.swift-snapshot-testing, where SwiftPM failed with duplicate key found: 'Package@swift-6.0.0.swift'. A similar failure showed up earlier for swiftlang.swift-docc-symbolkit with Package@swift-5.5.0.swift.

This was especially frustrating because the error happens before the CLI code is compiled or tested. It makes agents report that they cannot run focused tests because the worktree cannot get past dependency resolution.

Root Cause

The registry was returning alternate manifest links based on the manifest filenames in metadata or S3:

Package.swift
Package@swift-6.0.swift

That is valid only if the alternate manifest represents a distinct Swift tools version. Some packages, however, publish an alternate manifest whose declared tools version is equivalent to the default manifest after SwiftPM canonicalization.

For pointfreeco/swift-snapshot-testing, the default manifest and Package@swift-6.0.swift both declare Swift tools version 6.0. Newer SwiftPM/Xcode canonicalizes that to a three-component key such as 6.0.0, so the two manifests collide internally as:

Package@swift-6.0.0.swift

The archive itself does not need to contain two literal Package@swift-6.0.0.swift files. The duplicate is created by SwiftPM while normalizing equivalent manifest versions from the registry response.

Approach

I added Cache.Registry.ManifestVariants as the single place that understands package manifest variants:

  • parses alternate filenames such as Package@swift-5.9.swift
  • parses // swift-tools-version: declarations
  • canonicalizes tools versions to major.minor.patch
  • filters alternates whose canonical tools version matches the default Package.swift
  • filters duplicate alternates that canonicalize to the same tools version

That logic is applied in three places:

  • RegistryController uses it before generating Link headers for alternate manifests. This fixes the active registry response path that SwiftPM consumes.
  • ReleaseWorker uses it before persisting release manifest metadata. This prevents newly indexed packages from storing redundant manifest variants.
  • AlternateManifests uses it in the S3 fallback path. When metadata is missing or stale, fallback discovery now reads the root manifest alongside alternates so it can make the same canonical-version decision.

Unknown or unparsable tools versions are still preserved. The filter only drops an alternate when it can prove that the canonical tools version was already represented by the default manifest or by an earlier alternate.

Alternatives Considered

I considered treating this as a CLI/worktree issue and relying on tuist install --force-resolved-versions, which did unblock this one local checkout. That is only a workaround: fresh worktrees without a usable resolved file still hit the registry response and fail before generation or testing.

I also considered suppressing all alternate manifests. That would be too broad because alternate manifests are still useful for packages that genuinely need different manifests across Swift tools versions.

Exact filename de-duplication was not enough either. The observed failures are caused by SwiftPM canonicalization, so 6, 6.0, and 6.0.0 need to be compared as the same tools version.

Impact

After this cache service change is deployed, fresh worktrees should be able to run tuist install, generate the CLI workspace, and run focused CLI builds/tests without hitting this duplicate-manifest SwiftPM failure.

The change is intentionally cache-side rather than CLI-side because the registry is the source of the invalid manifest variant set. Fixing the response also helps every SwiftPM client that resolves through the Tuist registry, not just Codex worktrees.

Validation

I validated the cache change with focused tests:

mix test test/cache/registry/alternate_manifests_test.exs test/cache/registry/release_worker_test.exs test/cache_web/controllers/registry_controller_test.exs

Result:

89 tests, 0 failures

I also formatted the touched cache files:

mix format lib/cache/registry/manifest_variants.ex lib/cache/registry/alternate_manifests.ex lib/cache/registry/release_worker.ex lib/cache_web/controllers/registry_controller.ex test/cache/registry/alternate_manifests_test.exs test/cache/registry/release_worker_test.exs test/cache_web/controllers/registry_controller_test.exs

Finally, I verified the original developer workflow after unblocking this local checkout with the resolved-file workaround that is needed until the live cache service is deployed:

tuist generate tuist ProjectDescription --no-open
xcodebuild build -workspace Tuist.xcworkspace -scheme tuist CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=""

Result:

** BUILD SUCCEEDED ** [422.510 sec]
Comments

No GitHub comments yet.