can1357/oh-my-pi · error · ArchiveError
Archive member data is truncated
Error message
Archive member data is truncated
What it means
Thrown by readTarEntriesFromBuffer in packages/utils/src/ar/tar.ts after parsing a tar header when the member's declared size (padded to block alignment) extends past the end of the buffer. The header claims more data blocks than the byte array actually contains, so the library refuses to index the entry rather than return partial data. This is a data-integrity guard: it means the archive bytes are incomplete, not a bug in the caller's options.
Source
Thrown at packages/utils/src/ar/tar.ts:456
sawTerminator = true;
break;
}
if (!checksumMatches(buffer, offset)) throw new ArchiveError("Invalid or corrupt tar archive header");
const headerOffset = offset;
const typeFlag = String.fromCharCode(buffer[headerOffset + TYPEFLAG_OFFSET] || 0x30);
let size = readTarSize(buffer, headerOffset + SIZE_OFFSET);
let name = readTarString(buffer, headerOffset + NAME_OFFSET, NAME_LENGTH);
if (isUstarHeader(buffer, headerOffset)) {
const prefix = readTarString(buffer, headerOffset + PREFIX_OFFSET, PREFIX_LENGTH);
if (prefix) name = `${prefix}/${name}`;
}
let linkName = readTarString(buffer, headerOffset + LINKNAME_OFFSET, LINKNAME_LENGTH);
const mtime = readTarNumeric(buffer, headerOffset + MTIME_OFFSET, MTIME_LENGTH);
const rawMode = readTarNumeric(buffer, headerOffset + MODE_OFFSET, MODE_LENGTH);
const mode = Number.isSafeInteger(rawMode) && rawMode >= 0 ? rawMode : undefined;
offset += BLOCK_SIZE;
const dataBlocks = paddedSize(size);
if (dataBlocks > buffer.byteLength - offset) throw new ArchiveError("Archive member data is truncated");
const data = buffer.subarray(offset, offset + size);
if (typeFlag === "L") {
assertIndexSize(data.byteLength, limits, "GNU long-name metadata");
longName = readMetadataPath(data, "GNU long path", limits);
offset += dataBlocks;
continue;
}
if (typeFlag === "K") {
assertIndexSize(data.byteLength, limits, "GNU long-link metadata");
longLink = readMetadataPath(data, "GNU long link target", limits);
offset += dataBlocks;
continue;
}
if (typeFlag === "N") {
applyOldGnuNameRecords(data, entries, pendingLinks, limits);
offset += dataBlocks;
continue;View on GitHub (pinned to 9690622007)
Solutions
- Re-download or re-copy the archive and compare file sizes/checksums against the source.
- Extract the underlying file completely into memory before calling (check that the decompressed stream reached EOF, not just that read() returned).
- Validate the tar ends with the expected two zero blocks; a buffer ending mid-member is truncated.
- If the source is a multi-volume or split tar, concatenate all parts (cat part1 part2 ...) before parsing.
- Wrap the call in try-catch on ArchiveError and surface 'archive is incomplete/corrupt' to the user instead of retrying parse.
Example fix
// before: parse whatever bytes arrived
const buf = new Uint8Array(await partialResponse.arrayBuffer());
const entries = readTarEntriesFromBuffer(buf, options);
// after: verify completeness first
const buf = new Uint8Array(await response.arrayBuffer());
if (response.headers.get('content-length') && buf.byteLength !== Number(response.headers.get('content-length'))) {
throw new Error('download incomplete, not a parse error');
}
const entries = readTarEntriesFromBuffer(buf, options); Defensive patterns
Strategy: validation
Validate before calling
// verify archive completeness before parsing
const bytes = new Uint8Array(await Bun.file(archivePath).arrayBuffer());
if (bytes.byteLength < 1024 || bytes.byteLength % 512 !== 0) {
throw new Error(`archive size ${bytes.byteLength} looks truncated`);
}
const entries = readTarEntriesFromBuffer(bytes, { limits }); Try / catch
try {
const entries = readTarEntriesFromBuffer(bytes, { limits });
} catch (err) {
if (err instanceof ArchiveError && /truncated/i.test(err.message)) {
throw new Error('Archive is incomplete or corrupt; re-download it');
}
throw err;
} Prevention
- Compare downloaded size against Content-Length or a published checksum before parsing.
- Wait for the decompression stream to fully end before buffering tar bytes.
- Never split tar archives with head/dd/tail; use multi-archive tooling instead.
- Fail fast on ArchiveError — the bytes are corrupt, retrying parse cannot succeed.
When it happens
Trigger: Passing a Uint8Array/Buffer to readTarEntriesFromBuffer (or a stream that readTar fully buffered) where a member header's octal/PAX size field points beyond buffer.byteLength — e.g. a partially downloaded .tar, a mid-file truncation, or reading a multi-volume tar with only one volume's bytes.
Common situations: Interrupted downloads (gzip finished but tar tail missing), splitting a tar file with `head -c`, appending to a tar while it is still being written, or downloading from an S3/HTTP range request that cut the file short.
Related errors
- Archive member '${formatArchivePathForError(memberPath)}' is
- Archive sparse metadata is truncated
- Archive member '${formatArchivePathForError(name)}' is trunc
- Invalid archive: truncated data
- Invalid tar octal value: ${value}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/06ef64d008ae85a0.
Report an issue: GitHub.