can1357/oh-my-pi · error · ArchiveError
Invalid ZIP archive: malformed local header for '${memberPat
Error message
Invalid ZIP archive: malformed local header for '${memberPath}' What it means
Thrown during member read when the 30-byte local file header at the recorded offset is missing, shorter than 30 bytes, or does not begin with the local header signature 0x04034b50. The central directory pointed at a position that is not a valid local header, so the archive structure is corrupt or inconsistent. Raised as an ArchiveError for the specific member.
Source
Thrown at packages/utils/src/ar/zip.ts:406
async read(size: number, memberPath: string): Promise<Uint8Array> {
try {
assertArchiveMemberSize(Math.max(size, this.#compressedSize), memberPath, this.#limits);
if ((this.#flags & (ENCRYPTED_FLAG | STRONG_ENCRYPTION_FLAG)) !== 0 || this.#method === 99) {
throw new ArchiveError(`Encrypted ZIP member '${memberPath}' is not supported`);
}
if (SUPPORTED_METHODS[this.#method] !== true) {
throw new ArchiveError(`Unsupported ZIP compression method ${this.#method} for '${memberPath}'`);
}
const headerEnd = checkedEnd(
this.#localHeaderOffset,
30,
this.#source.size,
`local header for '${memberPath}'`,
);
const header = await this.#source.read(this.#localHeaderOffset, headerEnd);
if (header.byteLength !== 30 || readUInt32LE(header, 0) !== LOCAL_HEADER_SIGNATURE) {
throw new ArchiveError(`Invalid ZIP archive: malformed local header for '${memberPath}'`);
}
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);View on GitHub (pinned to 9690622007)
Solutions
- Re-obtain the archive and verify integrity (`unzip -t archive.zip`) — the file is structurally damaged
- Re-download the file; check its size/hash against the publisher's checksum
- If it came from a stream, ensure the complete bytes were captured (no truncation at end)
- Try `zip -FF broken.zip --out fixed.zip` to salvage the structure
Example fix
// before: reading a truncated download
const zip = await readZip(Bun.file('partial.zip')); // throws 3711
// after: verify first
const ok = (await Bun.file('full.zip').arrayBuffer()).byteLength === expectedSize; Defensive patterns
Strategy: validation
Validate before calling
const bytes = new Uint8Array(await file.arrayBuffer());
const off = findLocalHeaderOffset(bytes, member); // from central directory
if (off + 30 > bytes.byteLength || readU32(bytes, off) !== 0x04034b50) throw new Error('archive structurally damaged; re-download'); Try / catch
try {
const data = await zip.read(member);
} catch (err) {
if (err instanceof ArchiveError && err.message.includes('malformed local header')) {
throw new Error('ZIP is corrupt (local header missing) — re-download or repair with zip -FF');
}
throw err;
} Prevention
- Verify file size/checksum immediately after download, before parsing
- Never hex-patch zips; regenerate them from source files
- Avoid opening files mid-transfer; wait for copy/upload completion
- Run `unzip -t` as a preflight gate in pipelines
When it happens
Trigger: ZipMemberSource reads a member whose central-directory localHeaderOffset points into a region without the PK\x03\x04 signature — e.g. after in-place editing, byte truncation, or a wrong-offset central directory; also when the source was truncated so fewer than 30 bytes remain at that offset.
Common situations: Partially downloaded or interrupted ZIP transfers; files modified by tools that rewrite the central directory without relocating local headers; zips concatenated or stripped of leading bytes (some self-extracting archives); hand-patched archives.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Invalid ZIP archive: local and central compression methods d
- Invalid ZIP archive: ${what} has an invalid range
- Invalid ZIP archive: missing end of central directory
- Invalid ZIP archive: missing ZIP64 end of central directory
- Unsupported ZIP compression method ${this.#method} for '${me
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/54064ee181b29526.
Report an issue: GitHub.