hcengineering/platform · error

Empty chunk received ${emptyChunkRetries} times for blob ${n

Error message

Empty chunk received ${emptyChunkRetries} times for blob ${name} at offset ${written}/${size}

What it means

In the blob upload writer (writeTo), reading the source stream may repeatedly yield empty chunks. After maxEmptyChunkRetries empty reads, the writer ends the writable and throws this error including the blob name, bytes written vs. total size, and the retry count. It protects against infinite loops when a stream/socket keeps returning zero bytes without an error or EOF.

Source

Thrown at foundations/server/packages/client/src/blob.ts:78

            readable.on('data', (chunk) => {
              chunks.push(chunk)
            })
            readable.on('end', () => {
              readable.destroy()
              resolve()
            })
          })
          const chunk = Buffer.concat(chunks)

          // Check for empty chunk to prevent infinite loop
          if (chunk.length === 0) {
            emptyChunkRetries++
            if (emptyChunkRetries >= maxEmptyChunkRetries) {
              ctx.error('Empty chunk received multiple times, aborting', { name, written, size, emptyChunkRetries })
              await new Promise<void>((resolve) => {
                writable.end(resolve)
              })
              throw new Error(
                `Empty chunk received ${emptyChunkRetries} times for blob ${name} at offset ${written}/${size}`
              )
            }
            ctx.warn('Empty chunk received, retrying', { name, written, size, retry: emptyChunkRetries })
            await new Promise<void>((resolve) => setTimeout(resolve, 100 * emptyChunkRetries))
            continue
          }
          emptyChunkRetries = 0 // Reset on successful non-empty chunk

          await new Promise<void>((resolve, reject) => {
            writable.write(chunk, (err) => {
              if (err != null) {
                reject(err)
              }
              resolve()
            })
          })

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Check network stability/proxy timeouts between client and blob storage; retry the transfer
  2. Verify the source that produces the stream (URL, file, socket) actually serves the full blob and supports range reads at the failed offset
  3. Increase maxEmptyChunkRetries / adjust the backoff if transfers over slow links legitimately pause
  4. Resume the upload from the reported offset (written) rather than restarting from zero

Example fix

// before (transfer aborts after repeated empty chunks)
await client.blob.put(name, unstableStream)
// after (wrap with a retrying source and resume from offset)
await withRetry((attempt) => client.blob.put(name, makeFreshStream(attempt)), (err) => !String(err).includes('Empty chunk'), 2000)
Defensive patterns

Strategy: retry

Validate before calling

// sanity-check the source serves data before a long upload
const probe = await fetch(sourceUrl, { headers: { Range: 'bytes=0-0' } })
if (!probe.ok) throw new Error(`Source unavailable: ${probe.status}`)

Try / catch

try {
  await blobWriteTo(writable, source)
} catch (err) {
  if (/Empty chunk received/.test(err.message)) {
    const [, written, size] = err.message.match(/offset (\d+)\/(\d+)/) || []
    // resume from Number(written) with a fresh stream/connection
  } else throw err
}

Prevention

When it happens

Trigger: Uploading/downloading a blob whose underlying stream stalls and returns empty chunks (e.g. a broken or stalled HTTP/socket source) more than maxEmptyChunkRetries consecutive times; the source reports no data and no end-of-stream, so the retry loop exhausts.

Common situations: Flaky network or proxy cutting the connection mid-transfer while the reader doesn't emit an error; server-side stream hang; uploading large blobs over unstable connections in dev tools or domain processing.

Related errors


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