{"record":{"id":"b8f1e8559c1476e6","repo":"remix-run/remix","slug":"multipart-stream-not-finished","errorCode":null,"errorMessage":"Multipart stream not finished","messagePattern":"Multipart stream not finished","errorType":"exception","errorClass":"MultipartParseError","httpStatus":null,"severity":"error","filePath":"packages/multipart-parser/src/lib/multipart.ts","lineNumber":509,"sourceCode":"      if (availableLength >= this.#boundaryLength) {\n        return { kind: 'full', start }\n      }\n\n      return { kind: 'partial', start }\n    }\n\n    return { kind: 'none' }\n  }\n\n  /**\n   * Should be called after all data has been written to the parser.\n   *\n   * Note: This will throw if the multipart message is incomplete or\n   * wasn't properly terminated.\n   */\n  finish(): void {\n    if (this.#state !== MultipartParserStateDone) {\n      throw new MultipartParseError('Multipart stream not finished')\n    }\n  }\n}\n\nlet decoder: TextDecoder | undefined\n\nfunction decodeUtf8(input: Uint8Array): string {\n  decoder ??= new TextDecoder('utf-8', { fatal: true })\n  return decoder.decode(input as BufferSource)\n}\n\n/**\n * The decoded headers for a multipart part, keyed by lower-case header name.\n */\nexport interface MultipartHeaders {\n  readonly [name: string]: string | undefined\n}\n","sourceCodeStart":491,"sourceCodeEnd":527,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/multipart-parser/src/lib/multipart.ts#L491-L527","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before (client body missing terminator)\n--BOUNDARY\r\nContent-Disposition: form-data; name=\"f\"\r\n\r\nv\r\n\n# after\n--BOUNDARY\r\nContent-Disposition: form-data; name=\"f\"\r\n\r\nv\r\n--BOUNDARY--\r\n","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  parser.finish()\n} catch (error) {\n  if (error instanceof MultipartParseError && error.message === 'Multipart stream not finished') {\n    // truncated upload — ask the client to retry\n    return new Response('Upload was truncated', { status: 400 })\n  }\n  throw error\n}","preventionTips":["Ensure senders terminate bodies with --boundary--","Treat finish() failures as client/network truncation, not server bugs"],"tags":["multipart","truncated-stream","parser-state"],"backgroundTag":"multipart-stream-malformed","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}