can1357/oh-my-pi · error · ArchiveError

Invalid ar archive header field

Error message

Invalid ar archive header field

What it means

ar header fields are fixed-width ASCII. decodeAsciiField trims trailing spaces and validates every remaining byte is printable ASCII (0x20–0x7e); any control byte or high-bit/non-ASCII byte makes the header unparsable, so the library throws. Called for name, mode, mtime, and size fields.

Source

Thrown at packages/utils/src/ar/unix-ar.ts:54

			const bytes = await this.#source.read(this.#offset, this.#offset + size);
			if (bytes.byteLength !== size) {
				throw new ArchiveError(`Archive member '${memberPath}' is truncated`);
			}
			return bytes;
		} catch (error) {
			if (error instanceof ArchiveError) throw error;
			throw new ArchiveError(error instanceof Error ? error.message : String(error));
		}
	}
}

function decodeAsciiField(bytes: Uint8Array, offset: number, length: number): string {
	let end = offset + length;
	while (end > offset && bytes[end - 1] === 0x20) end--;
	let value = "";
	for (let index = offset; index < end; index++) {
		const byte = bytes[index]!;
		if (byte < 0x20 || byte > 0x7e) throw new ArchiveError("Invalid ar archive header field");
		value += String.fromCharCode(byte);
	}
	return value;
}

function parseOptionalNumber(value: string, radix: 8 | 10, field: string): number | undefined {
	if (value === "" || value === "-1") return undefined;
	const pattern = radix === 8 ? /^[0-7]+$/ : /^\d+$/;
	if (!pattern.test(value)) throw new ArchiveError(`Invalid ar archive ${field}`);
	const parsed = Number.parseInt(value, radix);
	if (!Number.isSafeInteger(parsed)) throw new ArchiveError(`Invalid ar archive ${field}`);
	return parsed;
}

function parseRequiredSize(value: string): number {
	const parsed = parseOptionalNumber(value, 10, "member size");
	if (parsed === undefined) throw new ArchiveError("Invalid ar archive member size");
	return parsed;

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the input is actually an ar archive (global magic '!<arch>\n') before parsing
  2. Re-download/restore the file and check its checksum
  3. Use a parser matching the archive's dialect (GNU vs BSD long-name tables)

Example fix

// before
const ar = await ArReader.create(Bun.file(maybeTar));
// after
const head = new Uint8Array(await Bun.file(maybeTar).slice(0, 8).arrayBuffer());
if (new TextDecoder().decode(head) !== "!<arch>\n") {
	throw new Error("Not an ar archive");
}
const ar = await ArReader.create(Bun.file(maybeTar));
Defensive patterns

Strategy: validation

Validate before calling

const MAGIC = "!<arch>\n";
const head = new TextDecoder("latin1").decode(await file.slice(0, 8).arrayBuffer());
if (head !== MAGIC) throw new Error(`Not an ar archive (got ${JSON.stringify(head)})`);

Try / catch

try {
	const ar = await ArReader.create(source);
} catch (err) {
	if (err instanceof ArchiveError && err.message === "Invalid ar archive header field") {
		// input is not a valid ar archive or is corrupted
	}
	throw err;
}

Prevention

When it happens

Trigger: Reading a file that isn't a valid ar archive (wrong magic, binary blob renamed to .a), a corrupted header, or a non-GNU/non-BSD ar variant with binary header fields.

Common situations: Passing a .tar, .zip, or object file (.o is actually valid ar — but a plain ELF binary is not) to the ar parser; truncated download landing on a header; byte-shifted reads after a bad offset.

Related errors


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