Skip to content

Bug: MODEL_CACHE context window collision when same model ID exists across multiple providers #14708

@sandieman2

Description

@sandieman2

Bug Description

When the same model ID (e.g., claude-opus-4-6) is defined in multiple custom providers with different context windows, the MODEL_CACHE in src/agents/context.ts silently overwrites entries — whichever provider loads last wins. This causes sessions to use the wrong context window, leading to premature context limit errors and compaction failures.

Root Cause

In src/agents/context.ts, the model cache is a simple Map<string, number> keyed by model ID only:

const MODEL_CACHE = new Map();
// ...
for (const m of models) {
    if (!m?.id) continue;
    if (typeof m.contextWindow === "number" && m.contextWindow > 0)
        MODEL_CACHE.set(m.id, m.contextWindow);  // Last write wins!
}

The lookup in resolveContextTokens() then uses just the model ID:

function resolveContextTokens(params) {
    return params.agentCfg?.contextTokens 
        ?? lookupContextTokens(params.model)  // Only checks model ID
        ?? DEFAULT_CONTEXT_TOKENS;            // Falls back to 200K
}

Reproduction

  1. Define two providers with the same model ID but different context windows:
{
  "models": {
    "providers": {
      "anthropic-1m": {
        "headers": { "anthropic-beta": "context-1m-2025-08-07" },
        "models": [{
          "id": "claude-opus-4-6",
          "contextWindow": 1048576
        }]
      },
      "anthropic-beta": {
        "models": [{
          "id": "claude-opus-4-6",
          "contextWindow": 200000
        }]
      }
    }
  }
}
  1. Set the primary model to anthropic-1m/claude-opus-4-6 (expecting 1M context)
  2. Observe that sessions use 200K context limit instead of 1M (because anthropic-beta loads after anthropic-1m and overwrites the cache entry)

Impact

  • Sessions hit context limits at 200K instead of 1M
  • Auto-compaction triggers repeatedly but cannot recover (compacts based on config's 1M window, but runtime enforces 200K)
  • Safeguard mode resets the conversation, losing all context
  • Users see: "Context limit exceeded. I've reset our conversation to start fresh"

Suggested Fix

Option A (minimal): Use provider/modelId as the composite cache key:

MODEL_CACHE.set(`${m.provider}/${m.id}`, m.contextWindow);

And update lookupContextTokens to accept the full provider/model ref.

Option B (defensive): When duplicate model IDs exist, take the max context window:

const existing = MODEL_CACHE.get(m.id);
if (!existing || m.contextWindow > existing) {
    MODEL_CACHE.set(m.id, m.contextWindow);
}

Option A is more correct (respects per-provider differences). Option B is simpler and prevents the "wrong direction" failure mode.

Workaround

Set agents.defaults.contextTokens explicitly in config to bypass the cache lookup entirely:

{
  "agents": {
    "defaults": {
      "contextTokens": 1048576
    }
  }
}

Environment

  • OpenClaw version: 2026.2.9 (stable)
  • File: src/agents/context.ts (compiled in dist/reply-*.js)
  • Function: resolveContextTokens() at line ~4533 in compiled output

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions