Skip to content

fix(context): uncataloged combo members no longer clamp the runtime combo limit to generic 128k - #15053

Merged
diegosouzapw merged 1 commit into
diegosouzapw:release/v3.8.51from
skygunner:fix/14931-combo-sibling-specific-limits
Sep 29, 2026
Merged

diegosouzapw merged 1 commit into
diegosouzapw:release/v3.8.51from
skygunner:fix/14931-combo-sibling-specific-limits

Conversation

@skygunner

@skygunner skygunner commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR (plain language)

The context guard resolves a combo's window as roughly "the smallest member's window". A member whose window nobody knows answered 128,000 anyway — a generic constant that has nothing to do with the deployment — and dragged the whole combo down to that guess. After this PR, an unknown member says "unknown" instead of guessing, so the combo runs at the smallest window it actually knows about, and only falls back to the generic 128,000 when no member's window is known.

This is the runtime sibling-side mirror of the rule #10734/#10793 already applied to advertised combos via getSourcedTokenLimit() — owner-confirmed in #14931 (root cause #2).

The incident, concretely (from #14931): combo coding-pool = qwen3.8-27b (uncataloged → generic 128000) + deepseek-v4-flash-0731 (catalog match → 1,000,000). Math.min picked 128,000, the guard 400'd a request the provider measured at 49,822 real tokens, and Codex CLI's retries turned that into a 503 storm. One log line told the story: Combo context limit: 128000 (source=combo-min).

This is Part 1 of a 3-part series on #14931 — Part 1: sibling-side limit resolution (this PR) · Part 2: estimator calibration against provider-reported usage · Part 3: proactive-compression log basis labels. Sibling PRs: #15054 and #15055. Please keep the issue open until all three land.

Change

getComboTargetTokenLimit() (its only caller is chatCore's combo-limit array) now resolves through getSourcedTokenLimit(): a target whose window resolves solely to the non-specific DEFAULT_LIMITS.default returns undefined, and chatCore's existing numeric filter drops it from the array. resolveComboContextLimit()'s precedence chain is untouched:

  1. combo context_length (operator declaration) →
  2. executing target's own specific window →
  3. min(...known sibling windows) — previously min(...all windows, unknowns = 128000) →
  4. generic fallback when nothing is known (unchanged, source=fallback).
scenario before after
executing member has a specific window its own window (step 2 early-return) no change
executing member unknown, a sibling is known (the incident) min(128000, sibling) → 128000 false kill min(known siblings) → sibling window
every member unknown 128000 (fallback) 128000 (fallback) — no change
combo has explicit context_length explicit value no change

Why inheriting a sibling's window is the right default

This answers the concern raised in the #14931 thread that dropping the generic value "turns a too-small limit into a too-large one" when the executing member is the unknown one:

  1. Error direction is not symmetric. A too-small guard limit produces a local 400 the client cannot recover from (the Codex retry storm in the issue). A too-large limit produces an upstream 400, which the existing combo failover already handles — that is exactly how the incident's traffic eventually got served.
  2. Information quality. 128,000 is a constant unrelated to the deployment. min(known siblings) is at least the real window of an operator-curated sibling in the same pool — for the incident combo that is 1,000,000 vs qwen's real 262,144: overly wide, but a far better prior than a blind guess.
  3. The precise levers are unchanged and documented: combo context_length, per-model context overrides / CONTEXT_LENGTH_* env, catalog entries. This PR only removes the baseless constant from the chain.

Testing

…ombo limit to generic 128k

Refs diegosouzapw#14931. Part 1 of a 3-part series on the context-guard false kill:
- Part 1 (this PR): sibling-side limit resolution
- Part 2: estimator calibration against provider-reported usage
- Part 3: proactive-compression log basis labels

The issue stays open until all three land.

getComboTargetTokenLimit() resolved every sibling through the
non-specific-aware getTokenLimit(), so a member with no catalog/registry/
heuristic window contributed the generic 128000 catch-all to
resolveComboContextLimit()'s Math.min() — the sibling-side mirror of the
asymmetry diegosouzapw#10734 closed for advertised combos via getSourcedTokenLimit().
One uncataloged member dragged the whole combo's guard down to a guess,
false-killing ~50k-real-token Codex requests (diegosouzapw#14931).

Now the helper returns undefined for known-source-less targets; chatCore's
existing numeric filter drops them, and a combo whose known siblings are
all larger inherits min(known sibling windows) instead of the baseless
128000. When every member is unknown the resolver still falls back to the
generic default on its own (source=fallback), and combo-explicit
context_length / target-specific windows keep their precedence unchanged.

A false-negative here is recoverable (upstream 400 -> combo failover)
while the false-positive it removes was not — see the diegosouzapw#14931 discussion
for the trade-off analysis.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants