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
- Verify the byte source size matches the real file/stream length before parsing.
- Re-download the archive and verify against publisher checksums.
- 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.
- 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
- Stat the file (or finalize the stream) before constructing the ByteSource so size is accurate.
- For remote sources, verify Content-Length matches the served bytes.
- Re-check size after any concurrent writes that could shrink the file.
- Keep size and read() consistent in custom ByteSource implementations.
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
- ASAR member '${formatArchivePathForError(memberPath)}' is tr
- Invalid LZH archive: truncated data
- Invalid ar archive: truncated member header
- Invalid ar archive: truncated member data
- Unable to read ARJ archive: ${error instanceof Error ? error
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/0a058dd5db8bb2e0.
Report an issue: GitHub.