can1357/oh-my-pi · error · ArchiveError

Invalid ZIP archive: truncated data for '${memberPath}'

Error message

Invalid ZIP archive: truncated data for '${memberPath}'

What it means

Thrown when the bytes actually read for a member are fewer than the declared compressed size — the data region extends past the end of the source. This means the archive is truncated: the central directory claims more payload bytes than exist. ArchiveError naming the member.

Source

Thrown at packages/utils/src/ar/zip.ts:426

			const localFlags = readUInt16LE(header, 6);
			if ((localFlags & (ENCRYPTED_FLAG | STRONG_ENCRYPTION_FLAG)) !== 0) {
				throw new ArchiveError(`Encrypted ZIP member '${memberPath}' is not supported`);
			}
			if (readUInt16LE(header, 8) !== this.#method) {
				throw new ArchiveError(
					`Invalid ZIP archive: local and central compression methods disagree for '${memberPath}'`,
				);
			}
			const dataStart = this.#localHeaderOffset + 30 + readUInt16LE(header, 26) + readUInt16LE(header, 28);
			const dataEnd = checkedEnd(dataStart, this.#compressedSize, this.#source.size, `data for '${memberPath}'`);
			if (this.#method === 0 && this.#compressedSize !== size) {
				throw new ArchiveError(
					`Invalid ZIP archive: size mismatch for '${memberPath}' (expected ${size}, got ${this.#compressedSize})`,
				);
			}
			const compressed = await this.#source.read(dataStart, dataEnd);
			if (compressed.byteLength !== this.#compressedSize) {
				throw new ArchiveError(`Invalid ZIP archive: truncated data for '${memberPath}'`);
			}
			const decoded = await decodeMember(compressed, this.#method, size, memberPath);
			if (decoded.byteLength !== size) {
				throw new ArchiveError(
					`Invalid ZIP archive: size mismatch for '${memberPath}' (expected ${size}, got ${decoded.byteLength})`,
				);
			}
			const actualCrc = crc32(decoded);
			if (actualCrc !== this.#crc) {
				throw new ArchiveError(`Invalid ZIP archive: CRC mismatch for '${memberPath}'`);
			}
			return decoded;
		} catch (error) {
			throw archiveError(error, `Failed to read ZIP member '${memberPath}'`);
		}
	}
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-download the archive and compare byte length against the server's Content-Length or a published checksum
  2. Check disk space / copy completion — the file on disk may be short
  3. For HTTP sources, ensure the full body was read before parsing (await the stream end)
  4. Use `unzip -t` to confirm which members are recoverable; extract those individually

Example fix

// before: parsing before body completes
const res = await fetch(url);
const zip = await readZip(Bun.file(await res.arrayBuffer())); // may throw 3715 if body short
// after: verify length
const buf = new Uint8Array(await res.arrayBuffer());
if (buf.byteLength !== Number(res.headers.get('content-length'))) throw new Error('truncated download');
const zip = await readZip(buf);
Defensive patterns

Strategy: validation

Validate before calling

const bytes = new Uint8Array(await file.arrayBuffer());
if (bytes.byteLength < declaredZipSize) throw new Error('archive truncated — re-download before parsing');

Try / catch

try {
  const data = await zip.read(member);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('truncated data')) {
    throw new Error('download incomplete; refetch with length verification');
  }
  throw err;
}

Prevention

When it happens

Trigger: ZipMemberSource read where source.read(dataStart, dataEnd) returns short because localHeaderOffset + 30 + nameLen + extraLen + compressedSize exceeds source size — typical of cut-off downloads, partial uploads, or a source buffer smaller than the archive.

Common situations: HTTP downloads interrupted before completion; files copied while still being written; fetching a zip over a flaky network and not verifying Content-Length; trimmed logs/attachments containing embedded zips.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/6f611a5c63b42efa. Report an issue: GitHub.