remix-run/remix · error · MultipartParseError
Multipart stream not finished
Error message
Multipart stream not finished
What it means
MultipartParser.finish() throws MultipartParseError('Multipart stream not finished') unless the parser reached its Done state, i.e. the closing --boundary-- delimiter was seen. Calling finish on a truncated or unterminated message surfaces the incompleteness instead of silently returning partial results.
Source
Thrown at packages/multipart-parser/src/lib/multipart.ts:509
if (availableLength >= this.#boundaryLength) {
return { kind: 'full', start }
}
return { kind: 'partial', start }
}
return { kind: 'none' }
}
/**
* Should be called after all data has been written to the parser.
*
* Note: This will throw if the multipart message is incomplete or
* wasn't properly terminated.
*/
finish(): void {
if (this.#state !== MultipartParserStateDone) {
throw new MultipartParseError('Multipart stream not finished')
}
}
}
let decoder: TextDecoder | undefined
function decodeUtf8(input: Uint8Array): string {
decoder ??= new TextDecoder('utf-8', { fatal: true })
return decoder.decode(input as BufferSource)
}
/**
* The decoded headers for a multipart part, keyed by lower-case header name.
*/
export interface MultipartHeaders {
readonly [name: string]: string | undefined
}
View on GitHub (pinned to 9696913134)
Solutions
- Ensure the sender writes the full body including the terminating --boundary-- delimiter
- Verify the boundary used to construct the parser matches the one in the headers
- Treat the error as a signal to retry the upload / return 400 to the client
Example fix
# before (client body missing terminator) --BOUNDARY Content-Disposition: form-data; name="f" v # after --BOUNDARY Content-Disposition: form-data; name="f" v --BOUNDARY--
Defensive patterns
Strategy: try-catch
Try / catch
try {
parser.finish()
} catch (error) {
if (error instanceof MultipartParseError && error.message === 'Multipart stream not finished') {
// truncated upload — ask the client to retry
return new Response('Upload was truncated', { status: 400 })
}
throw error
} Prevention
- Ensure senders terminate bodies with --boundary--
- Treat finish() failures as client/network truncation, not server bugs
When it happens
Trigger: finish() called after the body ended without the terminating boundary — truncated uploads, dropped connection bytes, a wrong boundary that never matches, or forgetting to write the final chunk.
Common situations: Network interruptions mid-upload, proxies cutting bodies at size thresholds, client bugs omitting the closing delimiter, or tests with hand-written bodies missing --boundary-- at the end.
Related errors
- Unexpected data after end of stream
- Request is not a multipart request
- Request body is empty
- Invalid Content-Type header: missing boundary
- Request is not a multipart request
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/b8f1e8559c1476e6.
Report an issue: GitHub.