can1357/oh-my-pi · error · ArchiveError

Invalid ZIP archive: size mismatch for '${memberPath}' (expe

Error message

Invalid ZIP archive: size mismatch for '${memberPath}' (expected ${size}, got ${decoded.byteLength})

What it means

Thrown when a member decompresses successfully but its decoded byte length does not equal the uncompressed size declared in the central directory. The codec output and the metadata disagree, so the result cannot be trusted. ArchiveError reporting expected vs decoded byte counts.

Source

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

			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}'`);
		}
	}
}

function parseCentralDirectory(
	source: ByteSource,
	directory: Uint8Array,
	info: CentralDirectoryInfo,

View on GitHub (pinned to 9690622007)

Solutions

  1. Test the archive with `unzip -t` — corruption will be reported; re-obtain a clean copy
  2. Re-extract from the original source and re-zip
  3. If you produce the archive, fix the writer to set the true uncompressed size in the central directory
  4. Treat as untrusted input: reject the file rather than attempting salvage

Example fix

// before: trusting a repaired-by-concatenation zip
const data = await readZipMember(zip, 'a.bin'); // throws 3716
// after: verify integrity first
// $ `unzip -t suspect.zip` && unzip -o suspect.zip 'a.bin' -d out/
const data = await Bun.file('out/a.bin').bytes();
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const data = await zip.read(member);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('size mismatch') && err.message.includes('got')) {
    throw new Error('decoded payload does not match declared size — archive untrusted/corrupt');
  }
  throw err;
}

Prevention

When it happens

Trigger: ZipMemberSource read where decodeMember(...) yields decoded.byteLength !== size — corrupted compressed stream producing a different-length payload, inconsistent central-directory size fields, or data altered after compression.

Common situations: Bit-flipped or truncated-then-padded deflate streams; zips assembled from mismatched parts; archives produced by buggy compressors writing wrong uncompressed-size fields; malicious zip bombs/malformed entries.

Related errors


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