You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Early responses to large uploads hang when the relay forwards a cloned request body #737
No credentials, account identifiers, project references, private content or actual photos are included.
Problem
An Edge Function returning an early error for a finite 2,097,153-byte upload can leave the HTTP client waiting until its own timeout. This prevents a bounded photo endpoint from delivering its HTTP 413 response. A minimal local function with no Auth, database, Storage or Flare code reproduces the issue. Even an immediate response without reading or explicitly cancelling the body can stall.
Environment and observations
Windows host with Docker Desktop; Supabase CLI 2.110.0.
CLI reports supabase-edge-runtime-1.74.2, compatible with Deno 2.1.4.
Normal CLI-generated relay, with no runtime patches, used for the failing tests.
Finite 2 MiB + 1 body: immediate return, awaited cancellation, unawaited cancellation and a streamed error response timed out after 5 seconds in the minimal reproduction. Connection: close on the response did not resolve it.
Fully consuming that same finite body before returning produced HTTP 413 in approximately 60 ms. This is a diagnostic control, not a proposed production mitigation: unrestricted draining would defeat the resource bound.
A bounded-drain control returned 413 for 2 MiB + 1 in 77 ms but a finite 4 MiB request timed out after 5 seconds when that drain budget was exceeded. This just moves the failure to a larger request.
The real authenticated photo endpoint independently times out after 20 seconds for both fixed-length and chunked 2 MiB + 1 requests. Exact 2 MiB reaches JPEG validation; ordinary photo upload and removal work.
Earlier hosted Beta checks showed the same two authenticated boundary timeouts. We cannot inspect the managed relay and do not claim its implementation has been proved identical to the local cause.
Local cause and counterfactual
The CLI-generated relay forwards new Request(t,e.clone()), leaving the original request branch unused. In Edge Runtime v1.74.2, UserWorker.fetch pipes the forwarded body to the user worker and waits for both the body-pipe promise and the response promise before releasing the response:
When the worker stops reading early, piping tries to cancel its source. Cancellation of the cloned branch can remain pending while the other branch is unconsumed. A standalone Web Streams model reproduces that pending wait and releases it when the unused branch is cancelled.
An earlier isolated diagnostic removed request cloning only in the generated local relay; all 26 endpoint checks then passed with unchanged application handlers. That diagnostic change was restored and was never deployed. Direct Deno with the same application handlers also passed those checks. These controls isolate the local forwarding/cancellation interaction; they do not establish a supported hosted workaround.
Minimal reproduction
The index.ts included below is the exact local diagnostic function. It uses no credentials or database. Its drain modes exist only as controls and must not be deployed as a public function.
In a disposable local Supabase project, put it at supabase/functions/photo-body-diagnostic/index.ts and configure [functions.photo-body-diagnostic] with verify_jwt = false.
Use local API port 58121, start the local stack and run supabase functions serve. The probe refuses bodies larger than 5 MiB and targets only 127.0.0.1:58121.
Wait until OPTIONS /functions/v1/photo-body-diagnostic returns 204. Do not edit watched function files during checks, because the CLI restarts the runtime.
Run node transport-probe.mjs return cancel cancel-nowait drain response-stream. Default body size is exactly 2 MiB + 1.
The early-response cases should promptly return 413; they instead time out. The finite full-drain control returns 413.
The bounded-drain mode also demonstrates that limiting discard work moves the same failure to larger bodies. Set PHOTO_PROBE_BYTES=4194304 for that finite 4 MiB control.
Assistance requested
Please confirm whether the managed Edge Functions gateway has the same forwarding/cancellation issue, and provide either a provider fix or a supported way to deliver an early 413 while preserving strict byte/time bounds on rejected input. Increasing the accepted photo limit, unrestricted body draining, editing generated local runtime files, and relying solely on client-side validation are not acceptable production fixes for this endpoint.
index.ts (complete reproduction file)
// Local-only transport experiment. Never package or deploy this function.Deno.serve(async(request)=>{constmode=newURL(request.url).searchParams.get('mode')??'return';if(request.method==='OPTIONS')returnnewResponse(null,{status: 204});constresponse=()=>newResponse('too large',{status:413,headers:{'Content-Type':'text/plain','Connection':'close'}});if(mode==='return')returnresponse();if(mode==='cancel'){awaitrequest.body?.cancel();returnresponse();}if(mode==='cancel-nowait'){request.body?.cancel().catch(()=>{});returnresponse();}if(mode==='drain'){constreader=request.body?.getReader();while(reader&&!(awaitreader.read()).done){}returnresponse();}if(mode==='bounded-drain'){letreadBytes=0;constreader=request.body?.getReader();while(reader){const{done,value}=awaitreader.read();if(done)break;readBytes+=value.length;if(readBytes>2*1024*1024+65536){awaitreader.cancel();break;}}returnresponse();}if(mode==='drain-response'){returnnewResponse(newReadableStream({asyncstart(controller){controller.enqueue(newTextEncoder().encode('too large'));constreader=request.body?.getReader();while(reader&&!(awaitreader.read()).done){}controller.close();}}),{status:413});}if(mode==='response-stream'){returnnewResponse(newReadableStream({start(controller){controller.enqueue(newTextEncoder().encode('too large'));controller.close();}}),{status:413});}if(mode==='throw')thrownewError('too large');returnresponse();});
Update: Supabase CLI PR #6580
merged the same minimal correction on 14 September 2026. The local relay now
constructs the forwarded request from req rather than req.clone(), with an
end-to-end regression covering an early HTTP 400 response while the request body
is left unread. The PR's reported CLI, stack and platform checks passed.
That resolves the proven local CLI forwarding cause, but it does not establish
that the managed Edge Functions relay uses or has deployed the same forwarding
change. Our hosted Beta checks previously reproduced both fixed-length and chunked
2 MiB + 1 boundary timeouts while the function should have returned HTTP 413.
Could a maintainer confirm whether the managed relay has received, or is scheduled
to receive, the equivalent non-cloning fix? If the hosted relay is owned outside
this repository, please route or transfer this report to the appropriate team. We
can rerun the existing boundary probes unchanged after deployment.
Fresh hosted regression on 2 October: authenticated fixed-length and chunked
2,097,153-byte uploads still time out after a 20-second client deadline instead
of delivering HTTP 413. Both finite 5 MiB variants also time out. We downloaded
the deployed function and verified its executable source against the reviewed
bounded handler before testing.
Controls pass: missing/invalid bearer return 401, exactly 2 MiB of invalid JPEG
input reaches validation and returns 415, normal JPEG upload preserves bytes,
authorised signing/delivery/removal work, and fixed/chunked 16 KiB+1 signing and
8 KiB+1 removal return 413. The focused matrix is 15/19 passed with four upload
responses unresolved. Every disposable fictional fixture was independently
removed; no credentials, account IDs, project references, real photos or private
content are included here.
The non-cloning correction in CLI PR 6580
is now included in stable 2.119.0. We also inspected the later PR 6830 in that exact tag:
it drains the incoming body abandoned by the worker before releasing the response.
That can make finite-body local probes pass, but the shown drain has no byte cap.
We have not run a new normal-runtime acceptance test against that release, and
do not claim it proves the managed implementation or bounded hosted rejection.
Can a maintainer confirm the managed relay's ownership/deployment status and
provide a supported way to deliver early 413 while preserving byte/time bounds
on rejected input? Please route this report to the managed Edge Functions team
if needed. Increasing the accepted limit, unrestricted application-body draining,
patching generated runtime code or relying only on client validation would not
meet this endpoint's acceptance criteria. We can rerun the same bounded hosted
probes after a correction.
Fresh hosted reproduction on 2 October 2026 from a Stripe webhook endpoint with a strict 1 MiB raw-body limit.
All requests were finite, at most 2 MiB, and used deliberately invalid or missing Stripe signatures. No authenticated billing operations or customer fixtures were involved.
Hosted results with a 10-second client deadline:
Small invalid signature: HTTP 400 in 263 ms.
Exactly 1 MiB, invalid signature: HTTP 400 in 1,017 ms.
Declared 1 MiB + 1 byte: timeout at 10,003 ms instead of HTTP 413.
Chunked 1 MiB + 1 byte: timeout at 10,001 ms instead of HTTP 413.
Declared 2 MiB: timeout at 10,001 ms instead of HTTP 413.
Missing signature with 1 MiB + 1 byte: timeout at 10,002 ms instead of HTTP 400.
We also tested the unchanged production body reader in an isolated Edge Runtime v1.74.1 container, with no database or credentials. The main worker used EdgeRuntime.applySupabaseTag and forwarded to a user worker:
Without request cloning, fixed-length 1 MiB + 1 returned HTTP 413 in 603 ms; paced chunked 2 MiB returned HTTP 413 in 477 ms.
With request cloning, both those cases exceeded a 5-second deadline.
A finite chunked 1 MiB + 1 control returned 413 even with cloning, so the local symptom depends on whether the request source has finished by the time rejection occurs.
Ten local body-boundary tests pass, including preservation of signed Unicode/whitespace payloads below and exactly at the limit. Removing application cancellation was previously tested and did not resolve the cloned-forwarding failure.
This isolates forwarding ownership in the local reproduction. We cannot inspect the hosted relay, so the identical hosted cause remains an inference.
Could the managed Edge Functions team confirm whether the hosted forwarding path has received the non-cloning correction from CLI PR #6580, or provide a supported way to release early responses with strict byte/time bounds? Unrestricted draining or increasing the accepted body limit would undermine the security boundary. We can rerun the same hosted matrix after a provider correction.
This report contains no credentials, project references, customer data, or private request identifiers.
We are seeing a closely related boundary failure on hosted Supabase Edge Functions, with SupabaseEdgeRuntime 1.76.0 identified in correlated logs.
Our hosted observations are:
Exactly 2 MiB (2,097,152 bytes) receives a verifiable HTTP response.
2 MiB + 1 byte produces no observable response headers before the client deadline.
The behavior occurs with both Node fetch and native HTTPS.
It also occurs for chunked input without Content-Length.
Correlated Auth requests return HTTP 200 with the Edge Runtime user agent, supporting that execution progressed into the function path.
The unchanged handler returns HTTP 400 with private/no-store headers in six isolated local boundary checks, including 2 MiB + 1, with no SQL calls.
We have not established that the hosted relay uses the same implementation described in #737, but the observed boundary is strikingly similar.
Could Supabase confirm:
Whether the hosted gateway/relay used with Edge Runtime 1.76.0 can still exhibit the forwarding/cancellation behavior described here;
Whether there is any hosted request-body limit around 2 MiB that is not currently documented;
What the supported pattern is for returning an immediate 400/413 with safe headers while enforcing a strict byte limit without unbounded draining;
Whether there is a known hosted workaround or planned fix.
We stopped further timeout probing and have preserved the existing security and input limits.
Activity
Luke-Rouse commentedon Sep 26, 2026
Update: Supabase CLI PR #6580
merged the same minimal correction on 14 September 2026. The local relay now
constructs the forwarded request from
reqrather thanreq.clone(), with anend-to-end regression covering an early HTTP 400 response while the request body
is left unread. The PR's reported CLI, stack and platform checks passed.
That resolves the proven local CLI forwarding cause, but it does not establish
that the managed Edge Functions relay uses or has deployed the same forwarding
change. Our hosted Beta checks previously reproduced both fixed-length and chunked
2 MiB + 1 boundary timeouts while the function should have returned HTTP 413.
Could a maintainer confirm whether the managed relay has received, or is scheduled
to receive, the equivalent non-cloning fix? If the hosted relay is owned outside
this repository, please route or transfer this report to the appropriate team. We
can rerun the existing boundary probes unchanged after deployment.
Luke-Rouse commentedon Oct 2, 2026
Fresh hosted regression on 2 October: authenticated fixed-length and chunked
2,097,153-byte uploads still time out after a 20-second client deadline instead
of delivering HTTP 413. Both finite 5 MiB variants also time out. We downloaded
the deployed function and verified its executable source against the reviewed
bounded handler before testing.
Controls pass: missing/invalid bearer return 401, exactly 2 MiB of invalid JPEG
input reaches validation and returns 415, normal JPEG upload preserves bytes,
authorised signing/delivery/removal work, and fixed/chunked 16 KiB+1 signing and
8 KiB+1 removal return 413. The focused matrix is 15/19 passed with four upload
responses unresolved. Every disposable fictional fixture was independently
removed; no credentials, account IDs, project references, real photos or private
content are included here.
The non-cloning correction in CLI PR 6580
is now included in stable 2.119.0. We also inspected the later
PR 6830 in
that exact tag:
it drains the incoming body abandoned by the worker before releasing the response.
That can make finite-body local probes pass, but the shown drain has no byte cap.
We have not run a new normal-runtime acceptance test against that release, and
do not claim it proves the managed implementation or bounded hosted rejection.
Can a maintainer confirm the managed relay's ownership/deployment status and
provide a supported way to deliver early 413 while preserving byte/time bounds
on rejected input? Please route this report to the managed Edge Functions team
if needed. Increasing the accepted limit, unrestricted application-body draining,
patching generated runtime code or relying only on client validation would not
meet this endpoint's acceptance criteria. We can rerun the same bounded hosted
probes after a correction.
emerginginv commentedon Oct 3, 2026
Fresh hosted reproduction on 2 October 2026 from a Stripe webhook endpoint with a strict 1 MiB raw-body limit.
All requests were finite, at most 2 MiB, and used deliberately invalid or missing Stripe signatures. No authenticated billing operations or customer fixtures were involved.
Hosted results with a 10-second client deadline:
We also tested the unchanged production body reader in an isolated Edge Runtime v1.74.1 container, with no database or credentials. The main worker used
EdgeRuntime.applySupabaseTagand forwarded to a user worker:Ten local body-boundary tests pass, including preservation of signed Unicode/whitespace payloads below and exactly at the limit. Removing application cancellation was previously tested and did not resolve the cloned-forwarding failure.
This isolates forwarding ownership in the local reproduction. We cannot inspect the hosted relay, so the identical hosted cause remains an inference.
Could the managed Edge Functions team confirm whether the hosted forwarding path has received the non-cloning correction from CLI PR #6580, or provide a supported way to release early responses with strict byte/time bounds? Unrestricted draining or increasing the accepted body limit would undermine the security boundary. We can rerun the same hosted matrix after a provider correction.
This report contains no credentials, project references, customer data, or private request identifiers.
cemavega commentedon Oct 4, 2026
We are seeing a closely related boundary failure on hosted Supabase Edge Functions, with SupabaseEdgeRuntime 1.76.0 identified in correlated logs.
Our hosted observations are:
We have not established that the hosted relay uses the same implementation described in #737, but the observed boundary is strikingly similar.
Could Supabase confirm:
We stopped further timeout probing and have preserved the existing security and input limits.