-
-
Notifications
You must be signed in to change notification settings - Fork 39.6k
Description
Summary
MODEL_CACHE in src/agents/context.ts is populated exclusively from modelRegistry.getAll() (API/catalog discovery) and never reads contextWindow values from models.providers[].models[] in openclaw.json. Operator overrides are silently ignored by all 16 callsites that resolve context limits through lookupContextTokens().
Environment
- OpenClaw: 2026.2.13
- Provider: OpenRouter (also affects any provider reporting incorrect context windows)
- File:
src/agents/context.ts
Steps to Reproduce
- Configure an OpenRouter model with an explicit
contextWindowoverride:{ "models": { "providers": { "openrouter": { "models": [{ "id": "anthropic/claude-opus-4-6", "contextWindow": 200000 }] } } } } - Start a session using that model
- Run
/status
Expected Behavior
Context window shows 200,000 — the operator-configured value. All runtime decisions (compaction, memory flush, session persistence) use this value.
Actual Behavior
Context window shows the API-discovered value (e.g., 1,000,000 or 2,000,000 from OpenRouter). The config contextWindow is never consulted by lookupContextTokens().
Root Cause
In src/agents/context.ts, the MODEL_CACHE population loop (inside the async IIFE at lines 11-27) reads exclusively from modelRegistry.getAll():
const modelRegistry = discoverModels(authStorage, agentDir);
const models = modelRegistry.getAll() as ModelEntry[];
for (const m of models) {
if (!m?.id) continue;
if (typeof m.contextWindow === "number" && m.contextWindow > 0) {
MODEL_CACHE.set(m.id, m.contextWindow);
}
}loadConfig() is called on line 14 but only used for ensureOpenClawModelsJson(cfg). The config's models.providers[].models[].contextWindow values are never written into MODEL_CACHE.
Impact
lookupContextTokens() is called from 16 sites across 12 files. Getting the wrong value is not a display bug — it controls runtime behavior:
| Subsystem | File | Effect of wrong value |
|---|---|---|
| Session defaults | gateway/session-utils.ts |
New sessions get wrong contextTokens |
| Compaction threshold | auto-reply/reply/model-selection.ts |
Compaction fires too late (inflated) or too early (deflated) |
| Session persistence | auto-reply/reply/directive-handling.persist.ts |
Wrong contextTokens stored in session record |
| Memory flush | auto-reply/reply/memory-flush.ts |
Flush timing miscalculated |
| Agent runner | auto-reply/reply/agent-runner.ts |
Wrong context limit for agent execution |
| Followup runner | auto-reply/reply/followup-runner.ts |
Wrong context limit for followup agents |
| Cron agents | cron/isolated-agent/run.ts |
Wrong context limit for scheduled runs |
| Status display | auto-reply/status.ts |
/status shows wrong context percentage |
| Session listing | commands/sessions.ts |
Session list shows wrong limits |
| Status summary | commands/status.summary.ts |
Summary shows wrong limits |
When inflated (e.g., 2M instead of 200K), compaction never fires and the session grows until the API rejects it with a context-length error.
Suggested Fix
After the API discovery loop populates MODEL_CACHE, do a second pass over config models.providers and overwrite cache entries where the operator has set contextWindow:
const providers = cfg.models?.providers;
if (providers) {
for (const provider of Object.values(providers)) {
if (!provider?.models) continue;
for (const model of provider.models) {
if (model?.id && typeof model.contextWindow === "number" && model.contextWindow > 0) {
MODEL_CACHE.set(model.id, model.contextWindow);
}
}
}
}This runs after the discovery loop, so operator config always wins. The semantics are simple: explicit config overrides implicit discovery.
Workaround
None that covers all callsites. resolveContextWindowInfo() (used by the embedded runner display path) does read config, but the 16 lookupContextTokens() callsites do not.
Related Issues
- models.providers[].contextWindow config override ignored by compaction trigger — always uses hardcoded catalog value #16083 — Different code path. That issue is about
model.contextWindowon the object passed tocreateAgentSession()(pi-agent internal compaction). This issue is about theMODEL_CACHEpopulation path that feeds 16 other callsites. - MODEL_CACHE silently overwrites context windows on provider conflicts (last-write-wins) #11629 — Different trigger. That issue is about multiple providers registering the same model ID with conflicting values (last-write-wins). This issue is about config values never being written at all — even with a single provider, the operator override is ignored.
- Bug: Model context window from discovery not used for compaction (race condition in MODEL_CACHE) #10168 — Orthogonal. That issue is about a timing race (async IIFE not completed before first lookup). This issue exists even when the race is won: the cache is populated but with the wrong values because config overrides are never applied.
- models list shows smaller context window for openai-codex/gpt-5.2 (~266k/272k) than expected 400k #8506, [Bug]: Sonnet 4.5 shows a context window of 2m tokens #4965 — Those report the symptom (inflated context windows from OpenRouter). This issue identifies the root cause in
MODEL_CACHEpopulation.