can1357/oh-my-pi · error · ArchiveError

Invalid ar archive: truncated ${what}

Error message

Invalid ar archive: truncated ${what}

What it means

readExact validates the requested byte range before reading: start/end must be safe non-negative integers with end <= source.size and end >= start. Any violation means the range exceeds the archive's data — reported as a truncated region named by `what` ('signature', 'member header', long names, etc.).

Source

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

			nameByteLength = decoded.byteLength;
			dataOffset += header.bsdNameLength;
			size -= header.bsdNameLength;
		} else if (header.rawName === "//") {
			metadataSize += header.physicalSize;
			assertIndexSize(metadataSize, options.limits, "index");
			longNames = readMemoryRange(bytes, payloadOffset, payloadEnd);
		}
		records.push({ name, nameByteLength, dataOffset, size, mtimeSeconds: header.mtimeSeconds, mode: header.mode });
		assertEntryCount(records.length, options.limits);
		position = payloadEnd + (header.physicalSize & 1);
		if (position > bytes.byteLength) throw new ArchiveError("Invalid ar archive: missing alignment byte");
	}
	return materializeEntries(records, longNames, memoryByteSource(bytes), options);
}

async function readExact(source: ByteSource, start: number, end: number, what: string): Promise<Uint8Array> {
	if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || end > source.size) {
		throw new ArchiveError(`Invalid ar archive: truncated ${what}`);
	}
	try {
		const bytes = await source.read(start, end);
		if (bytes.byteLength !== end - start) throw new ArchiveError(`Invalid ar archive: truncated ${what}`);
		return bytes;
	} catch (error) {
		if (error instanceof ArchiveError) throw error;
		throw new ArchiveError(error instanceof Error ? error.message : String(error));
	}
}

async function readUnixArImpl(source: ByteSource, options: FormatReadOptions): Promise<ArchiveIndexEntry[]> {
	if (!Number.isSafeInteger(source.size) || source.size < SIGNATURE.length)
		throw new ArchiveError("Invalid ar archive signature");
	readSignatureFromBuffer(await readExact(source, 0, SIGNATURE.length, "signature"));
	const records: RawArMember[] = [];
	let longNames: Uint8Array | undefined;
	let metadataSize = 0;

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the byte source size matches the real file/stream length before parsing.
  2. Re-download the archive and verify against publisher checksums.
  3. If reading from a custom ByteSource, ensure `size` returns the true current size and `read(start,end)` errors or returns short only on genuine EOF.
  4. Handle the ArchiveError and surface a 'file incomplete/corrupt' message to users.

Example fix

// before
const entries = await readUnixAr(source);
// after
if (source.size < 8) throw new Error('file too small to be an ar archive');
const entries = await readUnixAr(source);
Defensive patterns

Strategy: validation

Validate before calling

if (!Number.isSafeInteger(source.size) || source.size < 8) {
  throw new Error(`byte source unusable: size=${source.size}`);
}

Type guard

function isUsableSource(s: ByteSource): s is ByteSource & { size: number } {
  return Number.isSafeInteger(s.size) && s.size >= 8;
}

Try / catch

try {
  return await readUnixAr(source);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('truncated')) {
    throw new Error(`cannot read ${what}: source shorter than declared`);
  }
  throw err;
}

Prevention

When it happens

Trigger: readUnixArImpl requests a header or payload range beyond source.size — e.g. the file is shorter than the 8-byte signature, or a header near EOF implies a range past the end; also headerBytes/nameBytes callers passing out-of-range ranges.

Common situations: Reading over an HTTP range where source.size was misreported (chunked/unknown length); truncated local files; a size race where the file shrinks between stat and read.

Related errors


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