Skip to content

fix(responses): surface provider 413 as terminal context overflow - #3177

Merged
lidge-jun merged 1 commit into
devfrom
ingw/fix-3170-responses-413-context-overflow
Sep 1, 2026
Merged

lidge-jun merged 1 commit into
devfrom
ingw/fix-3170-responses-413-context-overflow

Conversation

@Ingwannu

@Ingwannu Ingwannu commented Sep 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • convert a confirmed streaming Responses input-size 413 into one terminal response.failed with context_length_exceeded, so Codex stops its 5/5 reconnect loop and can apply next-turn compaction
  • cover local input admission, direct passthrough, translated adapters, exhausted Anthropic image tightening, and the outer combo boundary without silently deleting prompts or images
  • preserve non-streaming 413 status/body fidelity and unrelated 4xx/5xx behavior; use a bounded proxy-owned failure message so an upstream 413 body cannot echo request content
  • document the transport decision and user-visible continuation behavior

Verification

Exact head 6d5eea6a163106232242af14335f69f34ca6cad5 on base 22a643a00b5974fa53b084a04491f60d56ec9ee2:

  • exact-head cross-platform CI — passed, including all four Linux shards, macOS, keyring checks, npm-global checks, storage policy, typecheck/gates, and React Doctor
  • bun run typecheck — passed
  • bun test ./tests/responses-context-overflow.test.ts — 6 passed, 0 failed
  • translator-budget production contract — 1 passed, 0 failed
  • bun run test:changed — 2,887 passed, 4 skipped, 0 failed across 147 import-connected files
  • cd docs-site && bun run build — passed, 409 pages built
  • local full repository suite — 16,952 passed, 16 skipped, with only the three shutdown-launcher.test.ts 20-second startup timeouts failing under the constrained runner; the same three failures reproduced unchanged on clean origin/dev at 22a643a00b5974fa53b084a04491f60d56ec9ee2
  • git diff --check origin/dev...HEAD — passed
  • all local validation used isolated temporary HOME, OPENCODEX_HOME, and CODEX_HOME, nice -n 10, and no more than two CPUs; protected runtime configuration hashes remained unchanged

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. Streaming 413 bodies are not relayed; non-streaming compatibility stays unchanged.

Closes #3170.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Sep 1, 2026
@lidge-jun
lidge-jun marked this pull request as ready for review September 1, 2026 17:08
@lidge-jun
lidge-jun self-requested a review as a code owner September 1, 2026 17:08
@lidge-jun
lidge-jun merged commit 0d6424f into dev Sep 1, 2026
22 checks passed
@lidge-jun
lidge-jun deleted the ingw/fix-3170-responses-413-context-overflow branch September 1, 2026 17:08
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-01T17:12:19.533891Z 6d5eea6 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6d5eea6a16

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// The bounded reader owns the original body, deadline, abort settlement, and lock.
// Unsafe partial data falls back to #452's non-empty status-only JSON.
const errorText = await readDisplaySafeErrorText(upstreamResponse, upstream.signal, "");
if (upstreamResponse.status === 413 && clientRequestedStream) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve 413 for translated non-streaming callers

When a request enters through /v1/chat/completions or /v1/messages with stream:false, both wrappers force the internal Responses body to stream:true, so this condition—and the equivalent translated-adapter condition below—converts the provider's 413 into an HTTP-200 Responses SSE failure. The wrappers then fold that failure into HTTP 502, changing a terminal client-input error into a retryable server error instead of retaining the upstream 413 contract documented by this change. Restrict the conversion to genuine streaming Responses ingress (for example via options.inboundWire) or carry the original caller's stream mode through the replay.

AGENTS.md reference: src/AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

lidge-jun pushed a commit that referenced this pull request Sep 1, 2026
…dies

Reimplements #3142 (thanks @olddonkey) with the guard off by default, and
adds the rebuild site and refusal shape that version was missing.

The measurement, local refusal, image diagnostics, body-observation release
and probe-lease handling are that PR's work and are kept.

Why default-off rather than the 15 MiB default: the only measured ceiling in
this codebase belongs to the WebSocket transport, and the comment recording it
says the same body still succeeds over HTTP SSE. #2473 acts on that by falling
back to HTTP rather than refusing, and #2426 records an 18.2 MB HTTP 200. A
15 MiB default would therefore refuse turns that work today - on canonical
ChatGPT as well as on Azure and custom Responses gateways whose limits were
never measured at all. An unset proxy now measures nothing and sends exactly
what it sends today.

Two fixes beyond the original:

- The stored/main pool 401 replay rebuilds its body and sent it unchecked. It
  is now guarded like every other build site; a replay is precisely when a
  grown payload reappears.
- A streaming refusal returns terminal response.failed / context_length_exceeded
  instead of a JSON 413. Codex treats HTTP 413 as a retryable transport error
  and resends the same oversized body, which is the loop this feature exists to
  stop. That is the contract the upstream-413 path already uses (#3177).

RequestLogContext gains a proxy-owned errorCode so a locally refused request is
named as such instead of being classified from a status with no upstream
message behind it.

Refs #3142. Related to #2511, which asks for per-provider downscaling and
pruning and is deliberately not implemented here.
lidge-jun added a commit that referenced this pull request Sep 1, 2026
…dies (#3196)

* feat(responses): opt-in ceiling for oversized outbound passthrough bodies

Reimplements #3142 (thanks @olddonkey) with the guard off by default, and
adds the rebuild site and refusal shape that version was missing.

The measurement, local refusal, image diagnostics, body-observation release
and probe-lease handling are that PR's work and are kept.

Why default-off rather than the 15 MiB default: the only measured ceiling in
this codebase belongs to the WebSocket transport, and the comment recording it
says the same body still succeeds over HTTP SSE. #2473 acts on that by falling
back to HTTP rather than refusing, and #2426 records an 18.2 MB HTTP 200. A
15 MiB default would therefore refuse turns that work today - on canonical
ChatGPT as well as on Azure and custom Responses gateways whose limits were
never measured at all. An unset proxy now measures nothing and sends exactly
what it sends today.

Two fixes beyond the original:

- The stored/main pool 401 replay rebuilds its body and sent it unchecked. It
  is now guarded like every other build site; a replay is precisely when a
  grown payload reappears.
- A streaming refusal returns terminal response.failed / context_length_exceeded
  instead of a JSON 413. Codex treats HTTP 413 as a retryable transport error
  and resends the same oversized body, which is the loop this feature exists to
  stop. That is the contract the upstream-413 path already uses (#3177).

RequestLogContext gains a proxy-owned errorCode so a locally refused request is
named as such instead of being classified from a status with no upstream
message behind it.

Refs #3142. Related to #2511, which asks for per-provider downscaling and
pruning and is deliberately not implemented here.

* docs(devlog): record live inventory after 3190

---------

Co-authored-by: jun <jun@lidge.dev>
@lidge-jun lidge-jun mentioned this pull request Sep 9, 2026
2 tasks done
tarunravi pushed a commit to tarunravi/opencodex that referenced this pull request Sep 14, 2026
…dge-jun#3177)

Co-authored-by: Ingwannu <ingwannu@users.noreply.github.com>
tarunravi pushed a commit to tarunravi/opencodex that referenced this pull request Sep 14, 2026
…dies (lidge-jun#3196)

* feat(responses): opt-in ceiling for oversized outbound passthrough bodies

Reimplements lidge-jun#3142 (thanks @olddonkey) with the guard off by default, and
adds the rebuild site and refusal shape that version was missing.

The measurement, local refusal, image diagnostics, body-observation release
and probe-lease handling are that PR's work and are kept.

Why default-off rather than the 15 MiB default: the only measured ceiling in
this codebase belongs to the WebSocket transport, and the comment recording it
says the same body still succeeds over HTTP SSE. lidge-jun#2473 acts on that by falling
back to HTTP rather than refusing, and lidge-jun#2426 records an 18.2 MB HTTP 200. A
15 MiB default would therefore refuse turns that work today - on canonical
ChatGPT as well as on Azure and custom Responses gateways whose limits were
never measured at all. An unset proxy now measures nothing and sends exactly
what it sends today.

Two fixes beyond the original:

- The stored/main pool 401 replay rebuilds its body and sent it unchecked. It
  is now guarded like every other build site; a replay is precisely when a
  grown payload reappears.
- A streaming refusal returns terminal response.failed / context_length_exceeded
  instead of a JSON 413. Codex treats HTTP 413 as a retryable transport error
  and resends the same oversized body, which is the loop this feature exists to
  stop. That is the contract the upstream-413 path already uses (lidge-jun#3177).

RequestLogContext gains a proxy-owned errorCode so a locally refused request is
named as such instead of being classified from a status with no upstream
message behind it.

Refs lidge-jun#3142. Related to lidge-jun#2511, which asks for per-provider downscaling and
pruning and is deliberately not implemented here.

* docs(devlog): record live inventory after 3190

---------

Co-authored-by: jun <jun@lidge.dev>
agentHits pushed a commit to agentHits/opencodex that referenced this pull request Sep 17, 2026
…dge-jun#3177)

Co-authored-by: Ingwannu <ingwannu@users.noreply.github.com>
agentHits pushed a commit to agentHits/opencodex that referenced this pull request Sep 17, 2026
…dies (lidge-jun#3196)

* feat(responses): opt-in ceiling for oversized outbound passthrough bodies

Reimplements lidge-jun#3142 (thanks @olddonkey) with the guard off by default, and
adds the rebuild site and refusal shape that version was missing.

The measurement, local refusal, image diagnostics, body-observation release
and probe-lease handling are that PR's work and are kept.

Why default-off rather than the 15 MiB default: the only measured ceiling in
this codebase belongs to the WebSocket transport, and the comment recording it
says the same body still succeeds over HTTP SSE. lidge-jun#2473 acts on that by falling
back to HTTP rather than refusing, and lidge-jun#2426 records an 18.2 MB HTTP 200. A
15 MiB default would therefore refuse turns that work today - on canonical
ChatGPT as well as on Azure and custom Responses gateways whose limits were
never measured at all. An unset proxy now measures nothing and sends exactly
what it sends today.

Two fixes beyond the original:

- The stored/main pool 401 replay rebuilds its body and sent it unchecked. It
  is now guarded like every other build site; a replay is precisely when a
  grown payload reappears.
- A streaming refusal returns terminal response.failed / context_length_exceeded
  instead of a JSON 413. Codex treats HTTP 413 as a retryable transport error
  and resends the same oversized body, which is the loop this feature exists to
  stop. That is the contract the upstream-413 path already uses (lidge-jun#3177).

RequestLogContext gains a proxy-owned errorCode so a locally refused request is
named as such instead of being classified from a status with no upstream
message behind it.

Refs lidge-jun#3142. Related to lidge-jun#2511, which asks for per-provider downscaling and
pruning and is deliberately not implemented here.

* docs(devlog): record live inventory after 3190

---------

Co-authored-by: jun <jun@lidge.dev>
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.

2 participants