Repository navigation
[Bug] Bun.serve has no maxRequestBodySize: >128 MiB /v1/responses bodies get an empty-body 413 that Codex renders as "Unknown error" #1601
Description
Summary
Bun.serve() in src/server/index.ts never sets maxRequestBodySize, so Bun's default 128 MiB cap applies to the /v1/responses HTTP listener. When Codex posts a body larger than that, Bun rejects it at the HTTP layer before fetch() runs, returning 413 with an empty body. Codex renders an empty error body as the opaque string Unknown error — exactly the failure mode #452 and src/server/responses/passthrough-error.ts were written to prevent, except this one happens one layer below anything opencodex controls.
Client-visible symptom:
unexpected status 413 Payload Too Large: Unknown error, url: http://127.0.0.1:10100/v1/responses
Because the handler never runs, the request leaves no trace at all: no usage.jsonl row, no service.log line. From the operator's side the proxy looks perfectly healthy (ocx health → Proxy healthy) while every turn of the affected session fails.
Environment
- opencodex
2.10.0(reproduced);2.14.1is still affected — see "Still present on latest" below - Bun
1.3.14(bundled) - Codex CLI
0.147.0-alpha.6.5 - macOS (darwin arm64)
websockets: falseinconfig.json, so Codex falls back to HTTP POST and always takes this path
Root cause
src/server/index.ts (2.10.0, L391):
const server: Server<WsData> = Bun.serve<WsData>({
port: listenPort,
hostname: bindHost,
idleTimeout: 255,
async fetch(req, requestServer): Promise<Response> {No maxRequestBodySize here, and rg maxRequestBodySize src/ returns nothing anywhere in the package. Bun therefore applies its default of 128 MiB and answers oversized requests itself.
Every 413 opencodex itself produces carries a JSON envelope with a populated error.message:
formatErrorResponse→classifyError(src/lib/errors.ts) always preservesmessagedecodeRequestErrorResponse→413 invalid_request_errorforDecompressedBodyTooLargeError413 request_too_largeforTranslatorBudgetExceededErrorformatPassthroughUpstreamErrorexplicitly re-wraps empty upstream bodies intoProvider error 413: (empty body)
So an empty-body 413 cannot originate from opencodex's own code paths. That is what makes the Bun layer identifiable as the source.
Evidence
-
No 413 anywhere in the proxy's own records. Status distribution across
~/.opencodex/usage.jsonl(10k+ requests):200: 10766 499: 319 502: 145 429: 19 400: 6 402: 2 413: 0service.loglikewise has no 413 entry. The request never reachedfetch(). -
The bodies really are over 128 MiB. Two Codex sessions that recorded the error, measured from their rollout JSONL:
rollout file size response_itemtotalimage-bearing tool outputs …17-17-29-019ffa32243 MB 199.9 MB 33 / 185.5 MB …13-40-27-019ff96b463 MB 272.6 MB 90 / 269.5 MB Individual
custom_tool_call_outputlines reach 25 MB / 9 MB / 3 MB — base64 images from Codex's built-inimage_gen, replayed as conversation history on every turn. zstd request compression only recovers the base64 overhead (~75% of raw), so the wire body still lands around 150 MB and stays over the cap. -
Both failures occurred on sub-agent turns — the error text is followed by
This agent's turn failed. If you still need this agent, use the available collaboration tools to give it another task.
Reproduction
- Run a Codex session that accumulates >128 MiB of conversation input (easiest: dozens of
image_gencalls; any large multimodal history works). - Route it through the opencodex proxy over HTTP (
websockets: false). - The next turn fails with
unexpected status 413 Payload Too Large: Unknown error, and nothing is written tousage.jsonlorservice.log.
Still present on latest
Checked the published 2.14.1 tarball. Bun.serve now takes a shared serveOptions object, which still does not set the field:
const serveOptions = {
idleTimeout: 255,
async fetch(req: Request, requestServer: Server<WsData>): Promise<Response> {rg maxRequestBodySize package/src/ → no matches. MAX_DECOMPRESSED_BODY_BYTES is still 256 * 1024 * 1024.
Impact
- The 128 MiB Bun default silently undercuts the 256 MiB
MAX_DECOMPRESSED_BODY_BYTESadmission cap the code intends to enforce. Requests between 128 MiB and 256 MiB are killed by Bun instead of reaching the guard that would explain itself. - The failure is undiagnosable from the proxy side: healthy
ocx health, zero log lines, zero usage rows. - The client message is the opaque
Unknown errorthat 503 from /v1/responses via Codex even though direct proxy curl to same endpoint returns 200 OK (Xiaomi MiMo provider, openai-chat adapter) #452's wrapping exists to eliminate.
Suggested fix
Set maxRequestBodySize on Bun.serve to at least MAX_DECOMPRESSED_BODY_BYTES, so the documented 256 MiB cap is the one that actually fires and rejections come back as a typed JSON error rather than an empty 413:
const serveOptions = {
idleTimeout: 255,
maxRequestBodySize: MAX_DECOMPRESSED_BODY_BYTES,
async fetch(...) { ... }
}Optionally make it configurable alongside appOwnedMemoryBudgetMb, and log a warning when a request is rejected for size so the operator has something to find.
Note this is distinct from #987, which concerned an upstream gateway's 10 MiB cap. This one is the local listener rejecting the request before any provider is contacted.
Activity
coderabbitai commentedon Aug 13, 2026
🔗 Related PRs
#463 - fix(proxy): stop empty 503 bodies becoming Codex Unknown error (
#452) [merged]#599 - fix: scope Spark cooldowns to its native quota [merged]
#749 - fix(responses): reject expired forward continuations [merged]
#922 - fix(codex): isolate provider host transport health [open]
#1412 - fix(responses): refuse oversized input and stop compounding replayed history [open]
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
github-actions commentedon Aug 13, 2026
Maintainer decision respected
A maintainer has reopened this issue. The automated closure has been deactivated.
lidge-jun commentedon Aug 13, 2026
Confirmed from the current
devcode: this is a valid defect and is not fixed ondev.src/server/request-decompress.ts:20definesMAX_DECOMPRESSED_BODY_BYTESas 256 MiB, but the sharedBun.serveoptions atsrc/server/index.ts:727-729set onlyidleTimeout. Both the public listener and the optional loopback listener spread those options atsrc/server/index.ts:1638-1650, so neither raises Bun's default body limit. The/v1/responseshandler begins only after the listener accepts the request (src/server/index.ts:1188-1216), which supports your conclusion that an over-limit request is rejected before OpenCodex can format or log its own error response.Thank you for the unusually complete report — the usage.jsonl status distribution and the per-rollout byte accounting made the 128 MiB boundary identifiable without a reproduction on our side.
Setting
maxRequestBodySize: MAX_DECOMPRESSED_BODY_BYTESon the shared options is the correct narrow fix. Linked PR #1636 has that production change but needs a real request in the 128-256 MiB range plus deterministic environment/teardown cleanup before merge; its current bodyless health check cannot prove this failure mode.DISPOSITION: ACCEPTED
lidge-jun commentedon Aug 14, 2026
Fixed by cherry-picking #1636 (fix(server): configure maxRequestBodySize to 256 MiB on Bun.serve listeners) onto dev at 50fe0d4e9.