paperclipai/paperclip · error · Error

Invalid zip archive: truncated file contents.

Error message

Invalid zip archive: truncated file contents.

What it means

Guard in readZipArchive's local-entry scan loop: a local file header was found, but the computed body end (30-byte header + name + extra + compressedSize) extends past the end of the uploaded bytes. The archive's file contents are truncated relative to its declared entry sizes, so the entry cannot be extracted safely.

Solutions

  1. Re-download the complete archive; file contents are truncated.
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at packages/shared/src/portability-zip.ts:254 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18). Data as JSON: /api/errors/00a06aac0b2c7113. Report an issue: GitHub.

Appendix: source

Thrown at packages/shared/src/portability-zip.ts:254

    if (offset + 30 > bytes.length) {
      throw new Error("Invalid zip archive: truncated local file header.");
    }

    const generalPurposeFlag = readUint16(bytes, offset + 6);
    const compressionMethod = readUint16(bytes, offset + 8);
    const compressedSize = readUint32(bytes, offset + 18);
    const fileNameLength = readUint16(bytes, offset + 26);
    const extraFieldLength = readUint16(bytes, offset + 28);

    if ((generalPurposeFlag & 0x0008) !== 0) {
      throw new Error("Unsupported zip archive: data descriptors are not supported.");
    }

    const nameOffset = offset + 30;
    const bodyOffset = nameOffset + fileNameLength + extraFieldLength;
    const bodyEnd = bodyOffset + compressedSize;
    if (bodyEnd > bytes.length) {
      throw new Error("Invalid zip archive: truncated file contents.");
    }

    localHeaderCount += 1;
    const rawArchivePath = textDecoder.decode(bytes.slice(nameOffset, nameOffset + fileNameLength));
    const archivePath = normalizeArchivePath(rawArchivePath);
    const isDirectoryEntry = /\/$/.test(rawArchivePath.replace(/\\/g, "/"));
    if (archivePath && !isDirectoryEntry) {
      const entryBytes = inflateZipEntry(compressionMethod, bytes.slice(bodyOffset, bodyEnd), maxEntryDecompressedBytes);
      totalDecompressedBytes += entryBytes.length;
      if (totalDecompressedBytes > maxTotalDecompressedBytes) {
        throw new Error(
          `Unsupported zip archive: decompressed contents exceed the ${maxTotalDecompressedBytes}-byte limit.`,
        );
      }
      entries.push({
        path: archivePath,
        body: bytesToPortableFileEntry(archivePath, entryBytes),
      });

View on GitHub (pinned to 120ae5428f)