hcengineering/platform · error · StorageError

Missing response body

Error message

Missing response body

What it means

StorageClientImpl.get fetches the blob URL and expects a streaming response body. Undici/Node fetch can produce a 200 response whose body is null (e.g. for empty or HEAD-like responses, or when the underlying connection is closed without a body); since a Readable cannot be constructed from null, the client throws StorageError('Missing response body').

Source

Thrown at foundations/core/packages/api-client/src/storage/client.ts:97

      _class: core.class.Blob,
      _id: objectName as Ref<Blob>,
      contentType: headers.get('Content-Type') ?? '',
      size: isNaN(size) ? 0 : (size ?? 0),
      etag: headers.get('ETag') ?? '',
      space: core.space.Configuration,
      modifiedBy: core.account.System,
      modifiedOn: isNaN(lastModified) ? 0 : lastModified,
      version: null
    }
  }

  async get (objectName: string): Promise<Readable> {
    const url = this.getObjectUrl(objectName)

    const response = await wrappedFetch(url, { headers: { ...this.headers } })

    if (response.body == null) {
      throw new StorageError('Missing response body')
    }
    return Readable.from(response.body)
  }

  async put (objectName: string, stream: Readable | Buffer | string, contentType: string, size?: number): Promise<Blob> {
    const buffer = await toBuffer(stream)
    const file = new File([new Uint8Array(buffer)], objectName, { type: contentType })
    const formData = new FormData()
    formData.append('file', file)
    let response
    try {
      response = await fetch(this.uploadUrl, {
        method: 'POST',
        body: formData,
        headers: { ...this.headers }
      })
    } catch (error: any) {
      throw new NetworkError(`Network error ${error}`)

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Check the object actually has content (use storageClient.stat and verify size > 0); handle zero-byte blobs separately.
  2. Verify the filesUrl/objectName resolves to the correct blob (a wrong URL can yield an empty 200 from a fallback route).
  3. Inspect proxy/ingress configuration for body stripping or buffering.
  4. Catch StorageError and fall back to re-fetching or returning an empty stream for legitimately empty objects.

Example fix

// before
const stream = await storage.get(blobId)
// after
const stat = await storage.stat(blobId)
if (stat === undefined || stat.size === 0) {
  return Readable.from([])
}
const stream = await storage.get(blobId)
Defensive patterns

Strategy: fallback

Validate before calling

// Check object existence/size before reading
const meta = await storage.stat(objectName)
if (meta === undefined) throw new Error(`Blob not found: ${objectName}`)
if (meta.size === 0) return Readable.from([]) // skip get() for empty blobs

Try / catch

try {
  return await storage.get(objectName)
} catch (e) {
  if (e instanceof StorageError && e.message === 'Missing response body') {
    return Readable.from([]) // treat as empty blob
  }
  throw e
}

Prevention

When it happens

Trigger: Calling storageClient.get(objectName) when the files service returns 200 with no body — commonly for a zero-byte object, a misconfigured proxy stripping the body, or a service bug where the body stream is not attached.

Common situations: Uploading empty files (0 bytes) and then reading them; an nginx/ingress config buffering or truncating responses; older fetch polyfills in browser contexts where response.body is null (browsers use response.body as ReadableStream differently).

Related errors


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