can1357/oh-my-pi · error · ArchiveError

ZIP member path '${name}' is too long to write

Error message

ZIP member path '${name}' is too long to write

What it means

Thrown by the ZIP writer when the UTF-8 encoded member path exceeds U16_MAX (65535) bytes, the maximum the ZIP local/central header name-length fields can hold. The name simply cannot be represented in the ZIP format.

Source

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

		const centralParts: Uint8Array[] = [];
		let localSize = 0;
		let centralSize = 0;
		let count = 0;
		for (const [inputName, data] of members) {
			const portableName = inputName.replace(/\\/g, "/");
			const normalizedName = normalizeArchiveEntryPath(portableName);
			if (
				!normalizedName ||
				normalizedName !== portableName.replace(/^\.\//, "") ||
				portableName.startsWith("/") ||
				/^[A-Za-z]:/.test(portableName) ||
				portableName.includes("\0")
			) {
				throw new ArchiveError(`Cannot write unsafe ZIP member path '${inputName}'`);
			}
			const name = normalizedName;
			const nameBytes = TEXT_ENCODER.encode(name);
			if (nameBytes.byteLength > U16_MAX) throw new ArchiveError(`ZIP member path '${name}' is too long to write`);
			if (data.byteLength >= U32_MAX) throw new ArchiveError(`ZIP member '${name}' is too large to write`);
			const deflated = data.byteLength === 0 ? undefined : zlib.deflateRawSync(data);
			const payload = deflated && deflated.byteLength < data.byteLength ? deflated : data;
			const method = payload === data ? 0 : 8;
			if (payload.byteLength >= U32_MAX || localSize >= U32_MAX) {
				throw new ArchiveError("ZIP archive is too large to write member offsets safely");
			}
			const checksum = crc32(data);
			const local = new Uint8Array(30 + nameBytes.byteLength);
			writeUInt32LE(local, 0, LOCAL_HEADER_SIGNATURE);
			writeUInt16LE(local, 4, 20);
			writeUInt16LE(local, 6, UTF8_FLAG);
			writeUInt16LE(local, 8, method);
			writeUInt16LE(local, 10, 0);
			writeUInt16LE(local, 12, 0x21);
			writeUInt32LE(local, 14, checksum);
			writeUInt32LE(local, 18, payload.byteLength);
			writeUInt32LE(local, 22, data.byteLength);

View on GitHub (pinned to 9690622007)

Solutions

  1. Shorten the member name before writing (truncate segments or hash long parts into a compact name)
  2. Strip or flatten deep directory prefixes so the path fits within 65535 UTF-8 bytes
  3. Validate name byte length (`Buffer.byteLength(name, 'utf8')`) before calling the writer and reject early

Example fix

// before
writeZip([{ name: `very/deep/prefix/${hugeGeneratedName}`, data }]);
// after
let name = `very/deep/prefix/${hugeGeneratedName}`;
if (Buffer.byteLength(name, 'utf8') > 65535) name = `very/deep/prefix/${hash(hugeGeneratedName)}`;
writeZip([{ name, data }]);
Defensive patterns

Strategy: validation

Validate before calling

function assertZipNameFits(name: string): void {
  if (Buffer.byteLength(name, 'utf8') > 65535)
    throw new Error(`member name too long (${Buffer.byteLength(name, 'utf8')} bytes): ${name.slice(0, 50)}...`);
}

Try / catch

try {
  await writeZip([{ name, data }]);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('too long to write')) {
    const short = `entry-${Bun.hash(name)}.bin`;
    await writeZip([{ name: short, data }]);
    return short;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling the zip write API with an absurdly long member name — typically from concatenating deep directory prefixes or from untrusted input containing a multi-kilobyte filename (each non-ASCII char costs up to 4 bytes).

Common situations: Generated names embedding long content (hashes, base64 blobs, URLs) into filenames; recursive directory trees near PATH_MAX with long segment chains; malicious uploads with oversized names.

Related errors


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