Title
Files larger than 1.5 GiB fail immediately; the chunked upload client and server disagree on the request format
Platform
Desktop App - Windows
Server Installation Method
Proxmox (Community Scripts)
Version
2.7.1
CLI Installation Method
None
CLI Version
No response
Troubleshooting
The Problem
Found while investigating bulk upload failures ( #1231 ). This one is independent and reproducible on its own with a single file.
The client switches to a chunked upload above 1.5 GiB (src/ui/api/ssh-file-operations-api.ts:444-497), building a multipart FormData body with no query string:
form.append("sessionId", sessionId);
form.append("path", path);
form.append("fileName", fileName);
form.append("chunkIndex", String(i));
form.append("totalChunks", String(totalChunks));
form.append("totalSize", String(file.size));
form.append("chunk", chunkBlob, fileName);
await getFileManagerApiForSession(sessionId)
.postForm("/ssh/uploadFileChunk", form, { timeout: 0 });
The server reads only req.query (src/backend/hosts/file-manager/content-routes.ts:1538-1558):
const sessionId = getRequiredQueryParam(req.query.sessionId as string);
const remotePath = getRequiredQueryParam(req.query.path as string);
const fileName = getRequiredQueryParam(req.query.fileName as string);
const offset = parseByteOffset(getRequiredQueryParam(req.query.offset as string));
const totalSize = parseByteOffset(getRequiredQueryParam(req.query.totalSize as string));
if (!sessionId || !remotePath || !fileName) {
req.resume();
return res.status(400).json({ error: "Missing sessionId, path, or fileName" });
}
I ran the route's exact parsing logic against the client's exact FormData shape:
POST /ssh/uploadFileChunk (multipart body, no query string)
server saw req.query = {}
-> HTTP 400 { error: 'Missing sessionId, path, or fileName' }
There are three separate contract mismatches:
- Body vs. query. The client sends fields in the multipart body; the server reads them from the query string. Guaranteed
400 on the first chunk, every time.
chunkIndex vs. offset. The client sends a chunk index; the server expects a byte offset. Different protocols.
- Raw body piped into the file. The server pipes the request body straight into the SFTP write stream (
content-routes.ts:1636). With a multipart body that would write --boundary delimiters and Content-Disposition headers into the destination file. The route is written for application/octet-stream (cf. express.raw({ limit: "5gb", type: "application/octet-stream" }) at index.ts:126), which is not what the client sends.
The client also ignores the nextOffset and complete fields the server returns, so there's no resume even in principle.
Net effect: every file over 1.5 GiB fails immediately, and the fallback specifically designed to rescue very large uploads is the one path that can never run.
How to Reproduce
- Open the file manager on any SSH host.
- Upload a single file larger than 1.5 GiB.
- It fails immediately. The server responds
400 {"error":"Missing sessionId, path, or fileName"} on the very first chunk.
Additional Context
There is no test coverage for this route — src/backend/tests/hosts/file-manager/ has no tests for uploadFileChunk, which is presumably why the mismatch went unnoticed.
Suggested fix
Pick one side of the contract and make both match:
- Option A — client sends params as a query string with a real byte
offset, and posts the chunk as a raw application/octet-stream body (matches the existing express.raw config and the server's req.pipe(writeStream)).
- Option B — server parses the multipart body with busboy, the way
uploadFileStream already does.
Either way, the client should then honour the returned nextOffset / complete so resume actually works.
Title
Files larger than 1.5 GiB fail immediately; the chunked upload client and server disagree on the request format
Platform
Desktop App - Windows
Server Installation Method
Proxmox (Community Scripts)
Version
2.7.1
CLI Installation Method
None
CLI Version
No response
Troubleshooting
The Problem
Found while investigating bulk upload failures ( #1231 ). This one is independent and reproducible on its own with a single file.
The client switches to a chunked upload above 1.5 GiB (
src/ui/api/ssh-file-operations-api.ts:444-497), building a multipart FormData body with no query string:The server reads only
req.query(src/backend/hosts/file-manager/content-routes.ts:1538-1558):I ran the route's exact parsing logic against the client's exact FormData shape:
There are three separate contract mismatches:
400on the first chunk, every time.chunkIndexvs.offset. The client sends a chunk index; the server expects a byte offset. Different protocols.content-routes.ts:1636). With a multipart body that would write--boundarydelimiters andContent-Dispositionheaders into the destination file. The route is written forapplication/octet-stream(cf.express.raw({ limit: "5gb", type: "application/octet-stream" })atindex.ts:126), which is not what the client sends.The client also ignores the
nextOffsetandcompletefields the server returns, so there's no resume even in principle.Net effect: every file over 1.5 GiB fails immediately, and the fallback specifically designed to rescue very large uploads is the one path that can never run.
How to Reproduce
400 {"error":"Missing sessionId, path, or fileName"}on the very first chunk.Additional Context
There is no test coverage for this route —
src/backend/tests/hosts/file-manager/has no tests foruploadFileChunk, which is presumably why the mismatch went unnoticed.Suggested fix
Pick one side of the contract and make both match:
offset, and posts the chunk as a rawapplication/octet-streambody (matches the existingexpress.rawconfig and the server'sreq.pipe(writeStream)).uploadFileStreamalready does.Either way, the client should then honour the returned
nextOffset/completeso resume actually works.