#12195: fix(agents): sync config fallback for lookupContextTokens cold-start race
agents
size: S
trusted-contributor
experienced-contributor
Cluster:
Model Configuration Fixes
## Summary
`lookupContextTokens()` uses a fire-and-forget async IIFE to populate `MODEL_CACHE`, but the cache lookup is synchronous. On first call — before async model discovery completes — the function returns `undefined`, causing all 12+ callers to fall back to `DEFAULT_CONTEXT_TOKENS` (200K).
This affects:
- **Status display**: Shows 200K instead of actual context window on cold start
- **Session persistence**: Persists incorrect 200K value to sessions.json
- **Cron sessions**: Consistently use 200K (never get the correct value)
### Fix
Add a synchronous config-backed fallback cache (`CONFIG_CACHE`) that reads `contextWindow` from `models.providers.*.models[]` in the user's config file. The cache is hydrated lazily on first fallback lookup and reused for subsequent calls.
Priority chain: `MODEL_CACHE` (async discovery) → `CONFIG_CACHE` (sync config) → `undefined` (caller provides DEFAULT_CONTEXT_TOKENS)
### Test plan
- [x] Write failing test proving `lookupContextTokens` returns `undefined` on cold start (before fix)
- [x] Test returns correct contextWindow from config for multiple providers
- [x] Test returns `undefined` for unknown model IDs
- [x] Test handles `undefined` and empty string model IDs
- [x] All 5 new tests fail before fix, pass after
- [x] `pnpm build` passes
- [x] `pnpm check` passes
Fixes #12158
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR fixes a cold-start race in `lookupContextTokens()` where the async model-discovery cache (`MODEL_CACHE`) is populated fire-and-forget, but the lookup is synchronous and can return `undefined` on first call. It adds a synchronous, lazily-hydrated fallback cache (`CONFIG_CACHE`) populated from the user config’s `models.providers.*.models[].contextWindow`, with priority `MODEL_CACHE → CONFIG_CACHE → undefined` (caller can then apply defaults).
It also adds a focused unit test suite that mocks model discovery to stay empty and verifies the config-backed fallback returns the expected contextWindow for multiple providers, and returns `undefined` for unknown/empty/absent model IDs.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk.
- The change is small and localized: it adds a deterministic synchronous fallback for a known cold-start race, and the new tests directly exercise the problematic startup path (async discovery empty) and expected fallback behavior. Previously raised issues about Vitest async mocking shape and config-cache population on transient load failures appear to have been addressed in the current commit.
- No files require special attention
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#14744: fix(context): key MODEL_CACHE by provider/modelId to prevent collis...
by lailoo · 2026-02-12
84.4%
#17604: fix(context): use getAvailable() to prevent cross-provider model ID...
by aldoeliacim · 2026-02-16
81.1%
#15632: fix: use provider-qualified key in MODEL_CACHE for context window l...
by linwebs · 2026-02-13
79.4%
#23136: fix: lookupContextTokens should handle provider/model refs
by patchguardio · 2026-02-22
79.1%
#15726: fix(sessions): use model contextWindow instead of agent contextToke...
by lailoo · 2026-02-13
79.0%
#6053: fix: use 400K context window instead of 200K if the model allows (g...
by icedac · 2026-02-01
78.6%
#13626: fix(model): propagate provider model properties in fallback resolution
by mcaxtr · 2026-02-10
78.5%
#16478: fix(gateway): fall back to lookupContextTokens on model switch
by colddonkey · 2026-02-14
78.4%
#11349: fix(agents): do not filter fallback models by models allowlist
by liuxiaopai-ai · 2026-02-07
77.9%
#17414: fix(sessions): refresh contextTokens when model override changes
by michaelbship · 2026-02-15
77.7%