Hive Hive
Sign in

fix(cli): memoize the auth token in ServerAuthenticationController to avoid a per-op keychain read

GitHub issue · Closed

Metadata
Source
tuist/tuist #11614
Updated
Jul 5, 2026
Domains
CLI
Details

What

Memoizes the resolved auth token inside ServerAuthenticationController, bounded by the token’s own expiry, so repeated authenticationToken() calls don’t re-resolve the credential on every call. Uses the controller’s existing shared CachedValueStore (the .current instance already used to coordinate refreshes), so the cache is process-wide and single-flight.

Why (root cause)

The CAS compilation-cache daemon resolves auth on every artifact request (its middleware calls authenticationToken() per request). On macOS the credential store is the keychain, so every call did a keychain read via securityd, which serializes under concurrency.

This surfaced while investigating why the Xcode CAS was net-negative on hosted macOS runners:

  • CAS analytics recorded ~340ms/op transfer_duration for ~78KB artifacts.
  • A packet capture on the host<->VM bridge showed the wire is ~1ms (handshake ~0.1ms, server processing ~0.2ms, transfer p90 ~0.3ms). So it is not the network.
  • Sampling the daemon mid-build put the hot path in ServerCredentialsStore.read (LocalAuthentication/KeychainCircle), not the network stack.

The gap in the controller: the valid-token path returned straight after a tokenStatus() keychain read, and cachedValueStore.getValue was applied only on the refresh branches. So a valid, non-expired token (the steady state during a build) was re-read from the keychain on every call.

What changed

authenticationToken(refreshIfNeeded: true) now wraps the resolution in cachedValueStore.getValue, keyed distinctly from the refresh lock, with an expiry shortly before the token’s own so the refresh/rotation path still runs in time (project tokens get a short TTL; an absent result is not memoized so a fresh login is picked up next call). Because it reuses the shared CachedValueStore, the memoization is process-wide and benefits every auth caller, not just the daemon. getValue’s single-flight means a concurrent burst performs one underlying read.

(An earlier revision of this PR used a decorator wired only into the CAS daemon; per review it was moved into the controller, which is the natural home and reuses the shared store.)

Impact

Removes the per-op keychain read from every hot auth path, chiefly the CAS daemon where it cost the most under load.

Validation

  • Compiles: narrow swift build --target TuistServer / TuistCAS both build.
  • Adds memoizes_a_valid_token_across_calls asserting that 10 resolutions read the credential store once.
  • The memoization primitive (CachedValueStore.getValue, single-flight + expiry) is existing, tested code. An earlier standalone run of the same pattern did one underlying read for 300 concurrent resolutions (0.0034s vs 0.902s when the read serializes like securityd).

Note: these are Tuist-generated test targets (run via tuist generate + xcodebuild / CI), not swift test targets, so CI is the authoritative test run.

Follow-up: once this reaches a canary, re-run the staging xcode-cache benchmark and confirm the recorded per-op transfer_duration collapses from ~340ms.

🤖 Generated with Claude Code

Flights

Investigate, reproduce, or fix this item in an isolated repository. Each Flight preserves its outcome and agent session.

New Flights are paused Configure model inference, GitHub, and a sandbox provider to start another Flight. Existing results remain available below.
No Flights yet

Start a Flight and preserve its objective, outcome, and session here.

Comments
T
tuist[bot] Jul 2, 2026

🛠️ Tuist Run Report 🛠️

Tests 🧪
Scheme Status Cache hit rate Tests Skipped Ran Commit
TuistAcceptanceTests 90 % 29 0 29 1d33cfea2
TuistUnitTests 91 % 3025 6 3019 1d33cfea2
Failed Tests ❌
Flaky Tests ⚠️
  • TuistUnitTests: 3 flaky tests (View all)
Test case Module Suite
parseTestStatuses_returnsCorrectStatuses() TuistXCResultServiceTests XCResultServiceTests
parseTestStatuses_extractsModuleAndSuiteNames() TuistXCResultServiceTests XCResultServiceTests
parseTestWithCustomLabelXCResult() TuistXCResultServiceTests XCResultServiceTests
Builds 🔨
Scheme Status Duration Commit
TuistAcceptanceTests 2m 30s 1d33cfea2
TuistUnitTests 3m 32s 1d33cfea2