Kraken’s nightly scan (at commit 79cd428) flagged PinProvider for using two unsynchronized var fields (cachedBearerToken, lastReadMs) in a concurrent context. The primary race was real and had already been fixed in PR #499 (commit b1aa83d93, 2026-06-17) before this issue was filed — that fix replaced the two fields with a single @Volatile CachedToken reference plus double-checked locking.
Investigating #552 surfaced a secondary visibility gap: PinProviderContainer.pinProvider was a plain var, not @Volatile. The field is written once during ServerLifecycleManager.onReady() and read from Ktor route-handler coroutines on potentially different threads. Without @Volatile, the JVM is not required to make the write visible to other threads.
PinProviderContainer exists so the Ktor auth plugin (which installs before Koin resolves) can reach the singleton PinProvider. Its holder field was plain var, relying on an implicit happens-before from Ktor’s lifecycle ordering rather than an explicit JVM memory barrier.
@Volatile to PinProviderContainer.pinProvider. The field is written once at startup and then only read, so the only cost is the initial read barrier — zero impact on throughput.PinProvider.refreshIfStale() was already fixed in PR #499; see docs/lessons/2026-06-17-pin-provider-concurrent-cache.md.@Volatile or written inside a synchronized block before the reader threads start. “Written once at startup” is not a substitute for a memory barrier.object singletons, check every var field: if it is ever written after object initialization and read on a different thread (even transitively via coroutines), add @Volatile.