chatboxai/chatbox · error

ZIP archive is truncated

Error message

ZIP archive is truncated

What it means

Thrown by validateZipEndOfCentralDirectory when the input file is smaller than 22 bytes — the minimum size of a ZIP End Of Central Directory record. A file that small cannot be a valid ZIP, so it is rejected before any parsing begins.

Source

Thrown at src/renderer/packages/backup/zip.ts:174

    }
  } finally {
    signal?.removeEventListener('abort', onAbort)
    zip.terminate()
    notifyProducer(true)
  }
}

export interface ReadZipEntry {
  path: string
  data: Uint8Array
  compressedSize?: number
  uncompressedSize: number
}

async function validateZipEndOfCentralDirectory(file: File) {
  const minimumRecordSize = 22
  const maximumCommentSize = 65_535
  if (file.size < minimumRecordSize) throw new Error('ZIP archive is truncated')
  const tailSize = Math.min(file.size, minimumRecordSize + maximumCommentSize)
  const tail = new Uint8Array(await file.slice(file.size - tailSize).arrayBuffer())
  let recordOffset = -1
  for (let index = tail.length - minimumRecordSize; index >= 0; index--) {
    if (tail[index] === 0x50 && tail[index + 1] === 0x4b && tail[index + 2] === 0x05 && tail[index + 3] === 0x06) {
      const candidateView = new DataView(tail.buffer, tail.byteOffset + index, tail.length - index)
      const commentLength = candidateView.getUint16(20, true)
      if (index + minimumRecordSize + commentLength === tail.length) {
        recordOffset = index
        break
      }
    }
  }
  if (recordOffset < 0) throw new Error('ZIP archive is missing its central directory')
  const view = new DataView(tail.buffer, tail.byteOffset + recordOffset, tail.length - recordOffset)
  const centralDirectorySize = view.getUint32(12, true)
  const centralDirectoryOffset = view.getUint32(16, true)
  if (centralDirectoryOffset + centralDirectorySize > file.size - (tail.length - recordOffset)) {

View on GitHub (pinned to 81571269ad)

Solutions

  1. Check file.size >= 22 (and realistically >= a few hundred bytes) before calling readZipFileEntries.
  2. Re-download or re-export the backup.
  3. Show a file-size/type guard in the import UI.

Example fix

// before
await readZipFileEntries(file, onEntry, options)

// after
const MIN_ZIP = 22
if (file.size < MIN_ZIP) throw new Error('File is too small to be a ZIP archive')
await readZipFileEntries(file, onEntry, options)
Defensive patterns

Strategy: validation

Validate before calling

const MIN_ZIP_EOCD_BYTES = 22
function isLargeEnoughToBeZip(file: File): boolean {
  return file.size >= MIN_ZIP_EOCD_BYTES
}

Try / catch

try {
  await readZipFileEntries(file, onEntry, options)
} catch (error) {
  if (/truncated/.test((error as Error).message)) notifyUser('Backup file is incomplete')
  throw error
}

Prevention

When it happens

Trigger: readZipFileEntries is called with a File whose size < 22 (e.g. an empty file, a 0-byte stub, or a tiny non-zip blob).

Common situations: User selects an empty or incompletely-downloaded file; a truncated upload; the wrong file type picked in the dialog.

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/aae8f61661fcc0b8. Report an issue: GitHub.