Repository navigation
Non-streaming 413 never reaches the terminal context-overflow mapping, so compaction fails with a raw 413 #4112
Description
Client or integration
Codex App
Area
Proxy and routing
Summary
Following up on #3675 with the SSE excerpt that was asked for. It turned out to be the residual route rather than the terminal mapping, so I am opening this separately as suggested.
The #3177 mapping works. It just never runs on the requests that are actually failing for me. The check at src/server/responses/core.ts:6255 requires clientRequestedStream, and every 413 in my logs is non-streaming, so all of them fall through to formatErrorResponse at :6301 and Codex receives a bare HTTP 413 Payload Too Large with code: "upstream_error".
The non-streaming requests are mostly compaction. Routed compaction builds its internal turn with stream: accountGatedCompactWireModel || route.combo ? true : false at src/server/responses/compact.ts:1015, which is false for GitHub Copilot. So once a session is large enough that compaction is triggered, the compaction request is itself over the provider limit, it gets rejected as an unclassified transport error, and the session cannot recover. That is the state I kept ending up in.
Reproduction
Same body, same provider, only stream differs. 5.95 MB of plain text, no images, to stay above the byte ceiling and below the token limit.
python3 - <<'PY'
import json
blob = "lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor. " * 80000
body = {"model": "github-copilot/claude-opus-5", "stream": True,
"input": [{"role": "user", "content": [{"type": "input_text", "text": blob}]}]}
open("big_stream.json", "w").write(json.dumps(body))
body["stream"] = False
open("big_nostream.json", "w").write(json.dumps(body))
PY
curl -sS -i -X POST http://127.0.0.1:10100/v1/responses \
-H 'Content-Type: application/json' --data-binary @big_stream.json
curl -sS -i -X POST http://127.0.0.1:10100/v1/responses \
-H 'Content-Type: application/json' --data-binary @big_nostream.json
stream: true is handled correctly. stream: false returns a raw 413.
Version
2.42.0
Operating system
macOS 26.2 Tahoe
Provider and model
github-copilot, claude-opus-5 (also reproduced on kimi-k3 and gpt-4o, so it is not model specific)
Logs or error output
stream: true, correct terminal event:
HTTP/1.1 200 OK
Content-Type: text/event-stream
event: response.created
data: {"type":"response.created","sequence_number":0,...}
event: response.in_progress
data: {"type":"response.in_progress","sequence_number":1,...}
event: response.failed
data: {"type":"response.failed","sequence_number":2,"response":{"status":"failed",
"error":{"message":"The provider rejected this turn because its input exceeds the provider size or context limit. Reduce the current input or compact the conversation before retrying.",
"type":"invalid_request_error","code":"context_length_exceeded"},"retryable":false}}
data: [DONE]
stream: false, same body, raw 413:
HTTP/1.1 413 Payload Too Large
Content-Type: application/json
{"error":{"message":"Provider error 413: Request Entity Too Large","type":"upstream_error","code":"upstream_error"}}
From ~/.opencodex/usage.jsonl, 106 records with status: 413 since 1 September, split across claude-opus-5 (51), kimi-k3 (51) and gpt-4o (4). Every one of them has closeReason: "non_stream" and errorCode: "http_413", and none has a terminalStatus. There is not a single 413 in the file with closeReason: "terminal", which is what the mapped path would produce via relay.ts:700. The only record in the whole log carrying PROVIDER_INPUT_TOO_LARGE_MESSAGE is a 400, not a 413.
One session shows the failure mode clearly. Conversation 88ff63518f, 5 September:
15:39:24 200 totalTokens=73895
15:39:29 413 non_stream Provider error 413
15:39:40 413 non_stream Provider error 413
... 16 more 413s over the next five minutes ...
15:47:03 400 terminal The provider rejected this turn because its input exceeds ...
105 successful turns climbing to roughly 74k tokens, then eighteen consecutive unclassified 413s as the session repeatedly tried and failed to compact, and only at the very end a single properly classified failure, which came from a 400 rather than a 413. The session was unusable from 15:39 onward.
What should OpenCodex do
Drop clientRequestedStream from the condition at core.ts:6255 and classify the non-streaming case too, returning JSON with type: "invalid_request_error" and code: "context_length_exceeded" instead of upstream_error. The streaming branch keeps returning streamingContextOverflowResponse exactly as it does now.
The status code and body shape for non-streaming can stay as they are if preserving 413 fidelity matters, since #3177 deliberately kept that. The part that matters is the classification: right now a provider size rejection and a generic upstream failure are indistinguishable on this path, so neither Codex nor the internal compaction caller at compact.ts:1029 can tell them apart. compact.ts only checks response.ok and propagates, so a classified error also gives compaction something it can act on rather than surfacing a transport error.
Additional context
This does not remove the need for #2511. Classifying the error correctly makes the failure survivable, but on Copilot nothing shrinks the request, so a large enough turn still cannot be sent at all. The one-tier image retry in src/server/image-retry.ts:40 is gated to adapterName === "anthropic" and Copilot runs through openai-chat, so image-heavy sessions have no path back under the limit.
Worth noting for sizing that work: normalizeImageTargets in src/adapters/anthropic-image-normalize.ts:380 is already wire-neutral and operates on generic NormalizeTarget handles, and src/adapters/kiro-images.ts reuses it for a completely different wire format. An openai-chat wrapper collecting image_url data URLs looks like it would be roughly the same size as the kiro one. The only structural obstacle is that createOpenAIChatAdapter().buildRequest at openai-chat.ts:1448 is synchronous while the normalizer is async. adapters/base.ts:38 already allows a promise return and every routed caller awaits it, but adapters/mimo-free.ts:227 casts the result synchronously and would need updating.
For measured numbers on this provider, Copilot appears to reject at roughly 5.2 MB of serialized body. Four screenshots at 4.44 MB went through, five at 5.47 MB did not, and plain text crosses at about the same point, so it is a byte limit and not a token or image one. Separately the token limit produces a clean 400 with a useful message, which is why the single correctly classified record above is a 400.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
DamnUi commentedon Sep 9, 2026
Yes i have kept in mind the other issue that was closed #3675
Ingwannu commentedon Sep 9, 2026
Confirmed a classification gap by source inspection on dev
b5c98333ff9a01c54172efd105516a34743bd9f3, without sending your payload to a live provider. Both the passthrough and translated response paths insrc/server/responses/core.tsgate their special 413 handling onclientRequestedStream. The noncanonical, non-combo compact path insrc/server/responses/compact.tscan request non-streaming output and returns a non-OK response unchanged.There is an existing regression explicitly preserving non-streaming 413 status/body, so simply removing the stream guard and returning the SSE helper would break JSON callers. A correction needs a JSON-compatible context-overflow classification while preserving HTTP 413, streaming behavior, error redaction, one-read handling, and combo attempt accounting. Both adapter paths and the compact caller need coverage.
This confirms the missing classification, not that classifying the error will reduce the request size or guarantee successful automatic compaction; the payload/compaction recovery question remains separate. Keeping this open for that narrow correction rather than treating it as a duplicate of the already-closed streaming report.
lidge-jun commentedon Sep 9, 2026
리뷰 · 우선순위 70 / 80
이 이슈는 non-stream 요청이 413을 받아도 “컨텍스트/크기 초과”로 분류되지 않는 구멍입니다. 지금
devHEAD(ad36c7be8)의src/server/responses/core.ts를 보면, 패스스루 경로(대략 5730줄 근처)와 번역 경로(대략 7466줄 근처) 둘 다upstreamResponse.status === 413 && clientRequestedStream일 때만streamingContextOverflowResponse로 보냅니다. 스트림이 아니면 일반 upstream 에러로 떨어집니다. 제보자의 curl 비교(같은 본문, stream만 다름)와 사용 로그(413이 전부closeReason: non_stream)가 그 구멍과 맞습니다.왜 컴팩션이 같이 깨지는지도 코드로 이어집니다.
src/server/responses/compact.ts의 routed 합성 컴팩션은stream: isCanonicalOpenAiForwardProvider(...) || accountGatedCompactWireModel || route.combo ? true : false입니다. GitHub Copilot처럼 그 조건이 거짓이면 컴팩션 턴 자체가 non-stream으로 나갑니다. 세션이 커져 컴팩션이 돌 때 provider가 413을 주면, 매핑이 안 된 raw 413이 그대로 돌아와response.ok만 보는 쪽에서 수송 오류처럼 보입니다. 스트림이면 #3177 매핑이 이미 잘 동작한다는 점도 제보와 Ingwannu 확인 댓글이 같게 말합니다.현재
dev와의 관계도 분명합니다. #4092 클라이언트 컴팩션·doctor latency·#4113 tool-schema 쪽과는 다른 축이고, 이미 닫힌 스트림 쪽 보고(#3675)의 잔여입니다. #2511(요청을 실제로 줄이기)과는 겹치되 대체는 아닙니다. 분류만 고쳐도 Codex와 compact 호출자가 “크기 초과”를 알아볼 수 있고, 요청을 줄이는 작업은 여전히 남습니다. Ingwannu가 소스 검사로 확인했고, 기존 회귀가 non-stream 413의 status/body를 지키도록 박아 두었으니 SSE 헬퍼를 그대로 쓰면 JSON 호출자를 깨뜨립니다. 고침은 “HTTP 413은 유지 + JSON에 context_length_exceeded(또는 동등 분류) + 스트림 동작·레닥션·one-read·combo 회계 유지”여야 합니다.라인 5730 근처 (core.ts 패스스루) -
413 && clientRequestedStream가드 때문에 non-stream 413이 컨텍스트 초과 매핑에 못 들어갑니다.라인 7466 근처 (core.ts 번역 경로) - 같은 가드가 한 번 더 있어, 두 경로 모두 고쳐야 구멍이 남기지 않습니다.
compact.ts stream 삼항 (약 1133줄) - Copilot 등 noncanonical 경로가 non-stream 컴팩션을 내어, 위 구멍과 겹치면 세션 복구가 멈춥니다.
이슈 본문이 가리킨 옛 6255줄 - HEAD에서는 줄 번호가 옮겨졌습니다. 조건 이름(
clientRequestedStream)으로 두 사이트를 찾으면 됩니다.#2511 / image-retry anthropic 게이트 - 이번 이슈의 필수 범위는 아닙니다. 분류 수정과 요청 축소는 나눠 다루는 편이 안전합니다.
메인테이너의 판단이 필요한 지점
code/type을 스트림 단말과 완전히 동일하게 맞출지, 최소한의 분류만 맞출지너의 추천
열어 두고,
clientRequestedStream가드만 빠진 좁은 수정 PR을 받으세요. 패스스루·번역 양쪽 + compact 호출 회귀가 필요합니다. SSE 헬퍼를 non-stream에 그대로 쓰지 말고, 413 status를 지키면서 JSON context-overflow 분류를 넣는 쪽이 Ingwannu 확인과도 맞습니다. #2511이나 openai-chat 이미지 노멀라이저는 후속으로 분리하세요.이 댓글은 grok-bot이 작성했습니다
Ingwannu commentedon Sep 9, 2026
Draft implementation is now #4127 at
493620254921f4e2b4173d64b2fa1974ba6f9354, based on current dev57077ca326. It keeps non-streaming HTTP 413/JSON while adding the context-overflow code, retains SSE behavior, and adds adapter/compact/combo regression assertions. The message is fixed and does not echo upstream request content. This does not reduce input or automatically retry compaction.git diff --checkand syntax-only checks passed; Bun product tests, typecheck and docs build have NOT run locally. Protected runtime files were unchanged. The PR is Draft pending exact-head CI and @lidge-jun's independent review, so this issue remains open.DamnUi commentedon Sep 9, 2026
my PR has been tested on local
feat(proxy): make the inbound body admission limit configurable
lidge-jun commentedon Sep 9, 2026
Fixed on by #4127 (merge 5669b96). Non-streaming provider input overflow now reaches the terminal context-overflow mapping, so a routed compaction request that is itself over the provider limit no longer surfaces as a raw 413 with and the session can recover. The related configurable inbound admission limit landed alongside it as #4138 (issue #3573). Closing manually because PRs here target .
feat(proxy): make the inbound body admission limit configurable