{"record":{"id":"6a0486fa591f94e1","repo":"hcengineering/platform","slug":"missing-response-body","errorCode":null,"errorMessage":"Missing response body","messagePattern":"Missing response body","errorType":"exception","errorClass":"StorageError","httpStatus":null,"severity":"error","filePath":"foundations/core/packages/api-client/src/storage/client.ts","lineNumber":97,"sourceCode":"      _class: core.class.Blob,\n      _id: objectName as Ref<Blob>,\n      contentType: headers.get('Content-Type') ?? '',\n      size: isNaN(size) ? 0 : (size ?? 0),\n      etag: headers.get('ETag') ?? '',\n      space: core.space.Configuration,\n      modifiedBy: core.account.System,\n      modifiedOn: isNaN(lastModified) ? 0 : lastModified,\n      version: null\n    }\n  }\n\n  async get (objectName: string): Promise<Readable> {\n    const url = this.getObjectUrl(objectName)\n\n    const response = await wrappedFetch(url, { headers: { ...this.headers } })\n\n    if (response.body == null) {\n      throw new StorageError('Missing response body')\n    }\n    return Readable.from(response.body)\n  }\n\n  async put (objectName: string, stream: Readable | Buffer | string, contentType: string, size?: number): Promise<Blob> {\n    const buffer = await toBuffer(stream)\n    const file = new File([new Uint8Array(buffer)], objectName, { type: contentType })\n    const formData = new FormData()\n    formData.append('file', file)\n    let response\n    try {\n      response = await fetch(this.uploadUrl, {\n        method: 'POST',\n        body: formData,\n        headers: { ...this.headers }\n      })\n    } catch (error: any) {\n      throw new NetworkError(`Network error ${error}`)","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/core/packages/api-client/src/storage/client.ts#L79-L115","documentation":"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').","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"// before\nconst stream = await storage.get(blobId)\n// after\nconst stat = await storage.stat(blobId)\nif (stat === undefined || stat.size === 0) {\n  return Readable.from([])\n}\nconst stream = await storage.get(blobId)","handlingStrategy":"fallback","validationCode":"// Check object existence/size before reading\nconst meta = await storage.stat(objectName)\nif (meta === undefined) throw new Error(`Blob not found: ${objectName}`)\nif (meta.size === 0) return Readable.from([]) // skip get() for empty blobs","typeGuard":null,"tryCatchPattern":"try {\n  return await storage.get(objectName)\n} catch (e) {\n  if (e instanceof StorageError && e.message === 'Missing response body') {\n    return Readable.from([]) // treat as empty blob\n  }\n  throw e\n}","preventionTips":["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."],"tags":["storage","blob","http","empty-body"],"backgroundTag":"missing-response-body","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}