can1357/oh-my-pi · error · ArchiveError

Invalid ar archive signature

Error message

Invalid ar archive signature

What it means

The input buffer does not begin with the Unix ar global magic ('!<arch>\n' / SIGNATURE). readSignatureFromBuffer runs before any member parsing and is the first structural validation, so this error means the file is not an ar archive at all.

Source

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

		const entry: ArchiveIndexEntry = {
			path,
			isDirectory,
			size: isDirectory ? 0 : record.size,
			...(record.mtimeSeconds !== undefined ? { mtimeMs: record.mtimeSeconds * 1000 } : {}),
			...(record.mode !== undefined ? { mode: record.mode } : {}),
			...(!isDirectory
				? { storage: { type: "member" as const, source: new ArMemberSource(source, record.dataOffset) } }
				: {}),
		};
		upsertArchiveEntry(entries, entry);
		assertEntryCount(entries.size, options.limits);
	}
	ensureParentDirectories(entries, options.limits);
	return [...entries.values()];
}

function readSignatureFromBuffer(bytes: Uint8Array): void {
	if (!sniffUnixAr(bytes)) throw new ArchiveError("Invalid ar archive signature");
}

/** Parse a fully materialized Unix ar archive for composition by formats such as deb. */
export function readUnixArEntriesFromBuffer(bytes: Uint8Array, options: FormatReadOptions): ArchiveIndexEntry[] {
	readSignatureFromBuffer(bytes);
	const records: RawArMember[] = [];
	let longNames: Uint8Array | undefined;
	let metadataSize = 0;
	for (let position = SIGNATURE.length; position < bytes.byteLength; ) {
		if (bytes.byteLength - position < HEADER_SIZE)
			throw new ArchiveError("Invalid ar archive: truncated member header");
		const header = parseHeader(readMemoryRange(bytes, position, position + HEADER_SIZE));
		metadataSize += HEADER_SIZE;
		assertIndexSize(metadataSize, options.limits, "index");
		const payloadOffset = position + HEADER_SIZE;
		const payloadEnd = payloadOffset + header.physicalSize;
		if (!Number.isSafeInteger(payloadEnd) || payloadEnd > bytes.byteLength) {
			throw new ArchiveError("Invalid ar archive: truncated member data");

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the file is really an ar archive: `file archive.a` should say 'current ar archive'.
  2. Check the first 8 bytes yourself for '!<arch>\n' before calling, to give a better error.
  3. Re-download the artifact; compare its size/hash with the publisher's checksum.
  4. Route the file to the correct parser (tar/zip) if it is another format.

Example fix

// before
const entries = await readUnixAr(bytes);
// after: sniff first
import { sniffUnixAr } from "@oh-my-pi/pi-utils";
if (!sniffUnixAr(bytes)) throw new Error(`${path} is not an ar archive`);
const entries = await readUnixAr(bytes);
Defensive patterns

Strategy: validation

Validate before calling

import { sniffUnixAr } from "@oh-my-pi/pi-utils";
if (!sniffUnixAr(bytes)) throw new Error(`${label}: not an ar archive (missing !<arch>\n magic)`);

Type guard

function isArMagic(head: Uint8Array): boolean {
  const magic = [0x21, 0x3c, 0x61, 0x72, 0x63, 0x68, 0x3e, 0x0a];
  return head.length >= 8 && magic.every((b, i) => head[i] === b);
}

Try / catch

try {
  return await readUnixArEntriesFromBuffer(bytes, opts);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('signature')) {
    throw new Error(`${label}: not a Unix ar archive`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling readUnixAr/readUnixArEntriesFromBuffer on a non-ar file: a tarball, .zip, text file, or an empty/1-byte file. sniffUnixAr returns false on the first bytes.

Common situations: Wrong file passed to a deb-composition path; a file with an .a/.deb extension that is actually a different format; HTML error page saved instead of a download; truncated download leaving fewer bytes than the signature.

Related errors


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