stop losing the answer when immich rejects mid-upload
An upload over a few MB came back as a 400 with an empty body, no message anywhere, and nothing logged by Immich, the sidecar or officer. It was a race, not a size limit. Immich judges an asset from its first few KB and rejects immediately, then closes; both our hops were still writing the body; Node treats the leftover bytes as a protocol violation and replaces the application's answer with a bodyless `400 Bad Request` + `Connection: close`. The real message never reached the wire. Measured before the change: streamed lost the message 1/4 at 8 MB and 4/4 at 32 MB — probability rising with size, which is why small photos usually worked and a phone's video never did. Both hops needed it. Fixing only the sidecar took 32 MB from 4/4 failing to 2/4, because the platform proxy was losing it one hop up. Bounded at 512 MB, above which the body streams exactly as before. That ceiling is not a refusal and is deliberately not a 413: a file Immich ACCEPTS is read to the end and never races, so a 4 GB video is unaffected. All that is given up above the cap is the error message on a file that was going to be rejected anyway. A first attempt refused over-cap uploads outright and would have broken the working 4 GB case to improve diagnosis of the doomed one. `bufferRequestBody` is opt-in and off by default: the vault and wallet proxies must keep streaming so a passphrase or macaroon never lands in the platform's heap. Also adds the proxy error logging that made this findable at all — status and two byte counts from headers, never the bodies. `responseBytes: "unknown"` is what exposed the stripped response. Verified live at 32/256 MB (buffered) and 640 MB (streamed, passes through). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,12 @@ const proxy = createSidecarProxy({
|
||||
// Originals and `download/archive` zips are large and Immich builds the archive as it streams it, so the
|
||||
// socket can sit quiet longer than the default 60s idle drop allows.
|
||||
timeoutSeconds: 600,
|
||||
// Immich judges an asset from its first few KB and rejects immediately, while we are still writing —
|
||||
// and the response is lost in the teardown, which is what made every large upload fail with an
|
||||
// unexplained empty 400. Buffering here removes the overlap. The sidecar does the same on its own hop
|
||||
// to Immich (sidecar/photos/routes.ts → readBodyCapped), and both are needed: fixing one alone still
|
||||
// lost the answer roughly two runs in three at 32 MB.
|
||||
bufferRequestBody: true,
|
||||
});
|
||||
|
||||
export const photosRouter = proxy.router;
|
||||
|
||||
Reference in New Issue
Block a user