hcengineering/platform · warning

missing body

Error message

missing body

What it means

handleMultipartUploadPart streams the request body as the part data. If req.readable is false — the body was already consumed, is empty, or the request was not sent with a streaming body — the handler responds 400 'missing body'. The part bytes themselves are the payload, so an unreadable body cannot be processed.

Source

Thrown at services/datalake/pod-datalake/src/handlers/multipart.ts:81

  res: Response,
  datalake: Datalake
): Promise<void> {
  const workspace = req.params.workspace as WorkspaceUuid
  const partNumber = req.query.partNumber as string

  let uuid: string
  let uploadId: string

  const upload = req.query.uploadId as string
  try {
    ;({ uuid, uploadId } = parseUploadId(upload))
  } catch (err: any) {
    res.status(400).send('invalid upload id')
    return
  }

  if (!req.readable) {
    res.status(400).send('missing body')
    return
  }

  const { bucket } = await datalake.selectStorage(ctx, workspace)

  const part = await bucket.uploadMultipartPart(ctx, uuid, { uploadId }, req, {
    size: Number.parseInt(req.headers['content-length'] ?? '0'),
    partNumber: Number.parseInt(partNumber)
  })

  ctx.info('multipart-part', { workspace, uuid, uploadId, partNumber })

  res.status(200).json(part)
}

export async function handleMultipartUploadComplete (
  ctx: MeasureContext,
  req: Request,

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Send the raw part bytes as the request body (do not wrap in JSON) and ensure the stream is not pre-consumed.
  2. Check no middleware reads req before the handler (e.g. a body parser applied to this route).
  3. Retry with a fresh request object rather than reusing a drained stream.

Example fix

// before
fetch(url, { method: 'PUT', body: JSON.stringify({ data: part }) })
// after
fetch(url, { method: 'PUT', headers: { 'Content-Type': 'application/octet-stream' }, body: partBuffer })
Defensive patterns

Strategy: validation

Validate before calling

if (part.byteLength === 0) {
  throw new Error('multipart part is empty; refusing to upload')
}

Type guard

function hasBody(body: unknown): body is BodyInit {
  return body !== undefined && body !== null && !(typeof body === 'string' && body.length === 0)
}

Try / catch

const res = await fetch(partUrl, { method: 'PUT', body: partStream })
if (res.status === 400 && (await res.text()) === 'missing body') {
  throw new Error('request body was empty or already consumed; resend with raw part bytes')
}

Prevention

When it happens

Trigger: Sending the part-upload request with an empty body, with Content-Length 0, or after another middleware/consumer already drained the stream; using a JSON body wrapper instead of raw binary body.

Common situations: HTTP clients that buffer/transform the body; proxies that reject or strip the body on chunked uploads; retry logic re-sending a consumed request object.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/339f8e83e936f981. Report an issue: GitHub.