What happened?
Note: This issue was investigated and summarized by an AI assistant (Claude Code)
under my supervision. All timings and cache observations were measured on my machine,
and I can re-run or verify anything on request.
On recent Tuist versions, every tuist generate recompiles ProjectDescriptionHelpers
and all project manifests, even when absolutely nothing changed between two
consecutive invocations. The manifest cache is written on every run (same cache keys,
files rewritten in place) but never reused. On 4.157.4 the cache works as expected.
Measured on the same repo, same commit, same machine, back-to-back runs of
tuist generate --no-open:
| tuist |
1st run |
consecutive runs |
| 4.157.4 |
63s |
10.3s / 12.5s ✅ |
4.171.3 (clean Tuist/.build) |
38s |
92.6s / 125.0s ❌ (flaky) |
| 4.183.1 |
– |
156.6s ❌ |
| 4.190.0 |
– |
157.6s ❌ |
| 4.203.1 |
161.8s |
163.2s / 155.0s / 178.7s ❌ |
What I ruled out:
- Environment instability — two consecutive
env | sort dumps from the exact same
invocation context are byte-identical, so environmentHash inputs are stable.
- Cache-key churn —
~/.cache/tuist/Manifests/1.<md5> filenames are stable across
runs (zero new names); the same entries get rewritten in place on every run, so the
write path computes the same keys while the read path rejects them.
- Manifest sandboxing — setting
generationOptions: .options(disableSandbox: true)
has no effect (still ~155s warm).
- Expected behavior: adding
--verbose changes the helpers cache key (TUIST_* env vars
feed the hash) — that part is fine.
Given CachedManifestLoader reuses a cached manifest only when cacheVersion,
tuistVersion and all of
manifestHash / helpersHash / pluginsHash / environmentHash / disableSandboxHash
match, and manifest files, tuist version, environment and the sandbox flag are provably
constant here, the failing comparison seems to be helpersHash (or pluginsHash)
being computed differently between store and lookup.
How do we reproduce it?
Project shape (private repo, but the shape should be all that matters):
- Workspace with 9 generated projects (1 app + 8 module projects), ~75 workspace refs
- 6 files in
Tuist/ProjectDescriptionHelpers
Tuist/Package.swift with 18 remote packages (39 resolved packages via tuist install)
- No plugins.
Tuist.swift only sets compatibleXcodeVersions.
Steps:
tuist install
tuist generate --no-open (first run — expected to be slow)
tuist generate --no-open again, changing nothing
- On 4.157.4 step 3 takes ~10s; on 4.171.3+ it takes roughly as long as step 2
(~155–180s here). Running with
--verbose shows Built ProjectDescriptionHelpers
on every run, followed by a long silent phase while every Project.swift recompiles.
- Observe
~/.cache/tuist/Manifests/* mtimes: the same entries are rewritten on every
run (cache is stored but never reused).
Happy to run a patched build, add debug logging, or bisect 4.158–4.170 if that helps
narrow it down.
Error log
No error — generation succeeds. Timing log from tuist generate --no-open --verbose
on a warm cache (4.203.1, total 203s):
00:29:18 Loading and constructing the graph
00:29:38 Built ProjectDescriptionHelpers in (20.431s) ← rebuilt every run
00:29:41 [external package targets ignored/loaded — fast]
[~163s of silence: every Project.swift manifest recompiles]
00:32:24 Transforming project ...
00:32:42 Total time taken: 203.763s
macOS version
26.5.2
Tuist version
4.203.1
Xcode version
26.3