Skip to content

fix(responses): refuse input that cannot fit the model context window - #1696

Closed
lidge-jun wants to merge 1 commit into
codex/usage-memory-roadmap-docsfrom
codex/m0-1-input-admission
Closed

fix(responses): refuse input that cannot fit the model context window#1696
lidge-jun wants to merge 1 commit into
codex/usage-memory-roadmap-docsfrom
codex/m0-1-input-admission

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

Layer 1 of the usage/memory stack. Refuses a turn whose estimated input cannot plausibly fit the model's context window, before any upstream I/O. Design doc: devlog/_plan/260814_usage_memory_roadmap/010_m0_1_input_admission.md (layer 0, #1693).

#1412 reported ~127k of real context compounding to 1.3M–1.6M tokens and crashing the proxy. A turn that large is rejected upstream anyway, so the round trip buys nothing — it spends auth resolution, host-circuit budget, and bandwidth to arrive at a worse error than we can produce locally.

The gate sits in handleResponsesInner after applyFinalRouteRequestNormalization (so it measures what will actually be sent, including replay expansion) and before the preAuthHostKey circuit block (so an oversized turn costs neither auth nor circuit budget).

Three deliberate non-behaviors, each of which was a review blocker:

It does not refuse compaction turns. Codex sends compaction_trigger because context is full (src/responses/parser.ts:355), and routed /v1/responses/compact re-enters the same handler with that trigger appended (src/server/responses/compact.ts:659). 413-ing those would tell the client to compact and then deny the compaction — a deadlock against the very limit the error reports.

It does not re-derive the context window. route.provider is already the routedProviderConfig output, which refuses registry merging when the transport does not match (src/router.ts:251), so a user provider that merely shares a built-in name keeps its own limits. Native models fall back to nativeOpenAiContextWindow — static maps only. That fallback is load-bearing: the canonical openai registry entry declares no context fields, so without it the gate would be inert on the default Codex route. Reading the Codex catalog was rejected because it costs existsSync + readFileSync + statSync even on a cache hit, and this is the request path.

It does not treat the estimate as exact. estimateTokens is a char-ratio heuristic whose CJK sampling aliases: cjkRatio samples every stride-th character, so a payload of 62-char records each starting with one Hangul character samples as 100% CJK while being 1.6% CJK, firing the 2.5-chars/token clamp instead of 4.0.

{"length":126046,"stride":62,"sampledRatio":1,"trueRatio":0.0161,"overshoot":1.6}

Since 4.0 / 2.5 = 1.6 is that branch's maximum divergence, ADMISSION_TOLERANCE is 2.5 — above the demonstrated overshoot, while #1412's 10x still clears it fourfold. A regression test pins that exact payload as admissible. Repairing cjkRatio is deliberately left alone: estimateTokens also feeds usage accounting and auto-compact (src/server/chat-completions.ts:140), so it needs its own change and its own tests.

Verification

  • bun test tests/input-admission.test.ts — 19 pass, 0 fail
  • bun run typecheck — clean
  • bun test tests/core-lab-boundary.test.ts — still green; the new core.ts import pulls no Lab module
  • The estimator overshoot was reproduced locally on Bun 1.3.14 before choosing the tolerance, not taken on faith

Notable test cases: a custom provider named openai does not inherit native limits; a routed provider/model id does not take the native fallback; a _compactionRequest turn is admitted at any size; ceiling resolution performs zero fs calls; the aliased CJK payload stays admitted.

Stack

Depends on #1693 (layer 0, docs). Review this PR's diff only.

# Layer PR
1 M0-1 input admission ← you are here this
0 roadmap docs #1693

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

#1412 reported ~127k of real context compounding to 1.3M-1.6M tokens and
crashing the proxy. A turn that large is rejected upstream anyway, so the
round trip buys nothing: it spends auth resolution, host-circuit budget, and
bandwidth to arrive at a worse error than we can produce locally.

The gate runs after final route normalization, so it measures what will
actually be sent including replay expansion, and before auth and the circuit,
so an oversized turn costs neither.

Three things it deliberately does not do.

It does not refuse compaction turns. Codex sends compaction_trigger BECAUSE
context is full, and routed /v1/responses/compact re-enters the same handler
with that trigger appended. Refusing them would tell the client to compact and
then deny the compaction.

It does not re-derive the context window. route.provider is already the
routedProviderConfig output, which refuses registry merging when the transport
does not match, so a user provider that merely shares a built-in name keeps its
own limits. Native models fall back to nativeOpenAiContextWindow, which reads
static maps only -- the canonical openai registry entry declares no context
fields, so without that fallback the gate would be inert on the default route.
Reading the Codex catalog was rejected: it costs existsSync + readFileSync +
statSync even on a cache hit, and this is the request path.

It does not treat the estimate as exact. estimateTokens is a char-ratio
heuristic whose CJK sampling aliases: a payload of 62-char records each
starting with one Hangul character samples as 100% CJK while being 1.6% CJK,
inflating the estimate 1.6x (measured: 126,046 chars, true ratio 0.0161).
Since 4.0/2.5 is exactly 1.6, that is the branch's maximum divergence, so the
2.5x tolerance sits above it and #1412's 10x still clears it fourfold. The
regression test pins that payload as admissible. Repairing cjkRatio is left
alone on purpose -- estimateTokens also feeds usage accounting and
auto-compact, so it needs its own change and its own tests.

Verified: bun test tests/input-admission.test.ts (19 pass), bun run typecheck,
and tests/core-lab-boundary.test.ts still green for the new core.ts import.
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6875180a-7785-41d8-949c-dc29c0558484

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Aug 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

lidge-jun added a commit that referenced this pull request Aug 14, 2026
Per-PR disposition for the open bug and bug-like PRs, with the A-gate corrections folded in: LAND the #1693/#1696/#1698 stack bottom-up, port a focused subset of #1625 by hand, keep #1703 blocked on a routing/privacy design decision, and leave #1655/#1660 open as feature-shaped changes.
@lidge-jun

Copy link
Copy Markdown
Owner Author

Landed on dev as babf254 (cherry-picked from 0298177). Verified that dev previously had context-window accounting but no pre-dispatch admission check: route normalization at core.ts:1840 went straight to circuit/auth admission with no size decision in between. The new src/server/responses/input-admission.ts resolves the ceiling and fails open when no ceiling is known. Verification on the landed commit: bun run typecheck clean, 137 pass / 0 fail across input-admission, continuation-dedup and responses-state, 222 pass / 0 fail across the widened regression slice. Closing in favour of the direct landing. 다음 배포에 포함됩니다.

@lidge-jun lidge-jun closed this Aug 14, 2026
@Wibias
Wibias deleted the codex/m0-1-input-admission branch August 15, 2026 19:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant