Tuist Registry#
Overview#
The Tuist Registry is Tuist's implementation of the Swift Package Registry specification, hosted at registry.tuist.dev. Instead of SPM's default behavior β deeply cloning every dependency repository β the registry serves pre-built source archives, so resolvers download only the commits they actually need .
Package availability is derived from the Swift Package Index: any package findable there is available in the Tuist registry . Archives are stored in Tigris S3 and distributed globally via edge storage for minimum latency .
Package identifiers follow the format {organization}.{repository}. Dots in repository names become underscores β e.g., groue/GRDB.swift β groue.GRDB_swift .
Setup & Authentication#
Automatic (recommended): Set registryEnabled: true in Tuist.swift; tuist generate creates the registry configuration file automatically .
Manual: Run tuist registry setup in your project directory .
Authentication is optional. Unauthenticated requests are rate-limited to 1,000 req/min per IP. Run tuist registry login to authenticate and get 20,000 req/min (requires a Tuist account and project) .
The --replace-scm-with-registry flag (set via installOptions in Tuist.swift, or passed directly to swift package resolve) makes SPM transparently route SCM URLs to their registry equivalents during resolution. See the CI guide for CI-specific setup.
Backend Architecture#
The registry backend is an Elixir service using Oban job queues. Two workers drive the sync pipeline:
-
Cache.Registry.SyncWorkerβ polls the Swift Package Index, compares GitHub tags against registry metadata, and enqueues aReleaseWorkerjob for each version not yet present. -
Cache.Registry.ReleaseWorkerβ the core per-version job. Its pipeline: fetch manifests from GitHub β fetch source archive (clone-with-submodules or zipball) β compute SHA-256 checksum β upload archive to S3 β upload manifests to S3 β update metadata .
Immutability assumption: Before syncing, ReleaseWorker checks (lines 70β75) whether the version already exists in releases or skipped_releases metadata. If present, the job exits immediately β published versions are treated as permanently immutable. This is a deliberate design choice (consumers lock checksums) but is the root cause of the stale-manifest failure mode described below.
Metadata storage: Cache.Registry.Metadata persists release data as JSON at registry/metadata/{scope}/{name}/index.json in S3. Cache.Registry.SyncCursor tracks sync progress at registry/state/sync_cursor.json.
Failure Mode 1: Stale Manifest (Mutated Upstream Tag)#
Root cause: Because ReleaseWorker skips versions already in metadata, force-pushing a semver tag upstream has no effect on the registry β it stays frozen on the pre-move content indefinitely .
Real incident (#11473): stephencelis/CSQLite@3.53.3 was synced before the upstream maintainer force-moved the tag to a clean commit. The registry kept serving the old Package.swift, which conditionally appended a product SwiftToolchainCSQLiteDynamic referencing a nonexistent target β but only when CI=true .
Error message:
error: 'stephencelis.csqlite': target 'SwiftToolchainCSQLite' referenced in product
'SwiftToolchainCSQLiteDynamic' could not be found; valid targets are: 'SQLiteSwiftCSQLite'
error: fatalError
Distinguishing symptom: CI fails (CI=true activates the broken manifest conditional); local dev resolves succeed . Failures are consistent β retrying does not help.
Immediate workaround: Pin to a known-clean version below the broken one (e.g., .package(id: "stephencelis.csqlite", exact: "3.50.4")) to unblock CI while a registry re-sync is arranged .
Permanent fix: Registry re-sync β see Re-sync Procedure below.
Failure Mode 2: Missing Source Archives (Storage Eviction)#
Real incident (#12098): Between July 23β26, 2026, source archives for most packages returned 404 while release metadata (checksums, version lists) continued to return 200 .
Error message:
error: 'airbnb.lottie-spm': failed downloading airbnb.lottie-spm version 4.6.1
source archive from https://tuist.dev/api/registry/swift: badResponseStatusCode(404)
error: fatalError
Distinguishing pattern: A selective loss where older versions of the same package still served (via 303 redirects to storage) while newer versions returned 404 β consistent with a storage eviction rather than a configuration change . Some packages also lost version metadata entirely: e.g., intercom.intercom-ios-sp listed only 6 stale versions (latest 19.3.4) on the registry while 19.7.1 was available on GitHub and the Swift Package Index .
Likely cause: Correlated with kura storage eviction/replication bugs (#12047, #12026, #11955) and a July 26 critical outage β if registry source archives live in that storage tier, the eviction bug would produce exactly this signature (metadata intact, blobs missing) .
Impact: Any machine without a warm SwiftPM cache cannot complete tuist install or swift package resolve . Authenticated requests return the same 404s, so this is not an auth issue.
Re-sync Procedure (Recovery)#
Prerequisites: Production Tigris rclone credentials and SSH access to a cache node .
Command:
mise run registry:sync <owner>/<package> <version>
Re-enqueues ReleaseWorker, which re-fetches the current upstream tag and rebuilds the manifest + source archive in place, replacing the stored artifacts and rewriting the catalog entry .
Verify the result:
curl -fsSL https://registry.tuist.dev/api/registry/swift/{scope}/{name}/{version}/Package.swift
Checksum protection: A re-sync that would produce a different checksum than what is already published is refused by default, because republishing different bytes turns a working pin into invalid registry source archive checksum for every client that already resolved the version. If rebuilding produces a different checksum, the job will fail with:
Refusing to republish {scope}/{name}@{version}: rebuilding produced {new_checksum} but {old_checksum} is already published.
To override β for example, when the stored archive is completely unextractable and no client ever resolved it successfully β pass allow_checksum_change: true:
Tuist.Registry.force_resync_swift_package_version(
"owner/package",
"1.2.3",
allow_checksum_change: true
)
Archive validation: The registry rejects archives whose directory entries do not have owner-execute permissions set. Such archives download and checksum correctly but fail during extraction with a permission error, as directory entries like ?rw-r--r-- extract to disk without the traversable bit and SwiftPM cannot descend into them. This validation prevents the failure mode reported with auth0.Auth0_swift@2.10.0.
Caveats:
- After re-sync, any client-side version pin added as a workaround (e.g.,
exact: "3.50.4") can be dropped once the target version resolves cleanly .