can1357/oh-my-pi · error · ArchiveError
Invalid CPIO archive: ${field} is not a valid base-${radix}
Error message
Invalid CPIO archive: ${field} is not a valid base-${radix} number What it means
parseDigits reads a fixed-width ASCII numeric header field of a CPIO entry and accumulates it digit by digit. The library throws this ArchiveError when a byte in the field is neither an ASCII digit valid for the field's radix (base-8 for odc/binary-style fields, base-16 hex for newc/new ASCII fields), meaning the archive header is corrupt or the format was misidentified. This is a strict structural validation: CPIO headers store all metadata fields as fixed-width ASCII numbers, so any stray byte breaks parsing.
Source
Thrown at packages/utils/src/ar/cpio.ts:113
!Number.isSafeInteger(end) ||
start < 0 ||
end < start ||
end > bytes.byteLength
) {
throw new ArchiveError(`Invalid CPIO archive: truncated ${what}`);
}
}
function parseDigits(bytes: Uint8Array, offset: number, length: number, radix: 8 | 16, field: string): number {
requireRange(bytes, offset, offset + length, `${field} field`);
let value = 0;
for (let index = offset; index < offset + length; index++) {
const code = bytes[index]!;
let digit: number;
if (code >= 0x30 && code <= 0x39) digit = code - 0x30;
else if (radix === 16 && code >= 0x41 && code <= 0x46) digit = code - 0x41 + 10;
else if (radix === 16 && code >= 0x61 && code <= 0x66) digit = code - 0x61 + 10;
else throw new ArchiveError(`Invalid CPIO archive: ${field} is not a valid base-${radix} number`);
if (digit >= radix) throw new ArchiveError(`Invalid CPIO archive: ${field} is not a valid base-${radix} number`);
value = value * radix + digit;
}
if (!Number.isSafeInteger(value)) throw new ArchiveError(`Invalid CPIO archive: ${field} is too large`);
return value;
}
function parseHeader(bytes: Uint8Array, offset: number): ParsedHeader {
requireRange(bytes, offset, offset + 2, "header");
const first = bytes[offset]!;
const second = bytes[offset + 1]!;
if ((first === 0xc7 && second === 0x71) || (first === 0x71 && second === 0xc7)) {
requireRange(bytes, offset, offset + BINARY_HEADER_SIZE, "old binary header");
const littleEndian = first === 0xc7;
const read16 = littleEndian ? readUInt16LE : readUInt16BE;
const read32Words = (fieldOffset: number): number =>
read16(bytes, offset + fieldOffset) * 0x10000 + read16(bytes, offset + fieldOffset + 2);
return {View on GitHub (pinned to 9690622007)
Solutions
- Regenerate the archive with a standard tool (cpio -H newc, or rpm2cpio for RPM payload) and re-download/verify checksums
- Verify you are feeding the right format — use sniffCpio on the buffer head before parsing
- Inspect the header bytes at the failing offset and confirm field encoding matches the format (hex for newc, octal for odc)
- If parsing RPM payloads, extract the cpio payload exactly (rpm2cpio) — do not pass raw RPM header bytes
Example fix
// before: parsing arbitrary bytes directly
const entries = await readCpio(source, options);
// after: sniff first and produce the archive with a standard format
const head = new Uint8Array(await source.slice(0, 6));
if (!sniffCpio(head)) throw new Error('not a supported CPIO archive');
const entries = await readCpio(source, options); Defensive patterns
Strategy: validation
Validate before calling
import { sniffCpio } from '<lib>/ar/cpio';
const head = new Uint8Array(await source.slice(0, 110));
if (!sniffCpio(head)) throw new Error('input is not a supported CPIO stream'); Type guard
function isAsciiDigits(bytes: Uint8Array, radix: 8 | 16): boolean {
return [...bytes].every(c =>
(c >= 0x30 && c <= 0x37) ||
(radix === 16 && ((c >= 0x41 && c <= 0x46) || (c >= 0x61 && c <= 0x66) || (c >= 0x38 && c <= 0x39))));
} Try / catch
try {
const entries = await readCpio(source, options);
} catch (err) {
if (err instanceof ArchiveError && err.message.includes('not a valid base-')) {
// treat as corrupt/non-CPIO input: fall back to another format reader or reject
} else throw err;
} Prevention
- Sniff the format (sniffCpio / magic bytes) before parsing
- Only feed the CPIO reader decompressed cpio payloads (rpm2cpio output), not raw RPM or tar bytes
- Verify file checksums after download to catch transfer corruption
- Prefer archives produced by GNU cpio or bsdtar over custom writers
When it happens
Trigger: Calling readCpio/readRpmArchive/entries on a buffer whose header contains a non-digit byte in an ASCII metadata field, e.g. a newc header where an 8-char hex field contains 'G'-'Z' or punctuation, or an odc 6/11-char octal field containing '8', '9', or a NUL/space byte. Also triggered when the parser aligns on a wrong offset after mis-detecting a magic, so it reads garbage as field bytes.
Common situations: Corrupted or truncated downloads of .cpio/.rpm payloads; archives produced by nonstandard tools that pad fields with spaces or NULs instead of leading zeros; attempting to parse a different archive format (tar, ar) that happens to be fed to the CPIO reader; hand-crafted or fuzzed archive bytes.
Related errors
- Invalid CPIO archive: member name is not NUL-terminated
- Invalid tar octal value: ${value}
- Invalid ARJ ${field}: missing terminator
- Invalid ARJ header signature
- Invalid ARJ basic header size
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/108caf73c7d7f5ff.
Report an issue: GitHub.