fix(relay): never forward a tunneled body that lost frames (#2822)

When the relay drops mid-request, the prompt_async body frames can be
lost. The tunnel host forwarded the request to loopback as an
empty/truncated chunked body, which the server rejects with a bare 400
(empty response body) — the mobile app's 'Failed to send message (400)'.

Host now buffers request bodies (<512KB) and forwards the complete body
only once StreamEnd arrives; larger bodies still stream live. A new
hasBody flag on the request head lets the host detect a body that
delivered zero frames and abort it as an ambiguous transport failure
(which the client already retries) instead of forwarding an empty body.
A 15s body-delivery deadline converts stalled tunnels into clean aborts.
This commit is contained in:
Muhammad Zaim
2026-08-12 16:54:23 +03:00
committed by GitHub
parent 9e43b9ae46
commit 6a2f0b8135
5 changed files with 274 additions and 31 deletions
+5
View File
@@ -61,6 +61,11 @@ export interface TunnelHttpRequestPayload {
path: string;
query: string;
headers: Record<string, string>;
/** True when the client had a request body to send. The host uses this to
* distinguish a genuine bodyless request from one whose body frames were lost
* through the tunnel (which it must abort as an ambiguous transport failure
* instead of forwarding an empty body the loopback server rejects with 400). */
hasBody?: boolean;
}
export interface TunnelHttpResponsePayload {
@@ -819,6 +819,7 @@ export const createRelayTunnelClient = (options: RelayTunnelClientOptions): Rela
path: request.path,
query: request.query,
headers: request.headers,
hasBody: request.body !== null,
};
channel.send(encodeTunnelFrame(TunnelFrameType.HttpRequest, streamId, encodeJsonPayload(head)));
void (async () => {