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
- Verify the input is actually an ar archive (global magic '!<arch>\n') before parsing
- Re-download/restore the file and check its checksum
- 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
- Sniff the '!<arch>\n' magic before handing a file to the ar parser
- Verify file integrity (checksum/size) after transfer
- Don't feed tar/zip/ELF binaries to the ar reader
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
- Archive file '${normalizedPath}' has no readable storage
- Archive member '${memberPath}' is truncated
- ${error.message}
- Invalid Content-Length header: ${value}
- archive destination exists: ${destSession}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/b8d830d821c0b9b1.
Report an issue: GitHub.