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
- Check the object actually has content (use storageClient.stat and verify size > 0); handle zero-byte blobs separately.
- Verify the filesUrl/objectName resolves to the correct blob (a wrong URL can yield an empty 200 from a fallback route).
- Inspect proxy/ingress configuration for body stripping or buffering.
- 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
- Use stat() to check size before downloading; special-case zero-byte objects.
- Avoid proxies/ingress configs that strip or buffer response bodies.
- Keep the fetch runtime (Node undici) up to date — old polyfills return null bodies.
- Pin objectName values from trusted sources; verify blob ids before fetch.
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
- Cannot create message, group not found: cardId = ${event.car
- Storage error ${error.error}
- text (response body)
- Failed to delete file
- Failed to delete file
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/6a0486fa591f94e1.
Report an issue: GitHub.