can1357/oh-my-pi · error · ArchiveError
Archive sparse metadata is truncated
Error message
Archive sparse metadata is truncated
What it means
Thrown while parsing a GNU sparse member (type flag 'S'): when the sparse-map 'isextended' byte is set, the header is followed by continuation blocks listing sparse extents, and this error fires when the buffer ends before the continuation chain completes. It means the archive's sparse metadata is cut off mid-chain, so the member's real data offset can't be located. Like the other truncation guards, it indicates incomplete archive bytes rather than bad options.
Source
Thrown at packages/utils/src/ar/tar.ts:506
if (longName !== undefined) name = longName;
if (longLink !== undefined) linkName = longLink;
const paxPath = paxAttribute(globalPax, localPax, "path");
if (paxPath !== undefined) name = paxPath;
const paxLinkPath = paxAttribute(globalPax, localPax, "linkpath");
if (paxLinkPath !== undefined) linkName = paxLinkPath;
const paxSize = paxAttribute(globalPax, localPax, "size");
if (paxSize !== undefined) size = parsePaxSize(paxSize, "member size");
const paxSparseName = paxAttribute(globalPax, localPax, "GNU.sparse.name");
if (paxSparseName !== undefined) name = paxSparseName;
let displaySize = size;
const paxSparseRealSize = paxAttribute(globalPax, localPax, "GNU.sparse.realsize");
if (paxSparseRealSize !== undefined) displaySize = parsePaxSize(paxSparseRealSize, "sparse real size");
const sparse = typeFlag === "S" || paxDeclaresSparse(globalPax, localPax);
if (typeFlag === "S" && buffer[headerOffset + GNU_SPARSE_ISEXTENDED_OFFSET] === 1) {
let extended = true;
while (extended) {
if (offset + BLOCK_SIZE > buffer.byteLength) {
throw new ArchiveError("Archive sparse metadata is truncated");
}
extended = buffer[offset + GNU_SPARSE_CONT_ISEXTENDED_OFFSET] === 1;
offset += BLOCK_SIZE;
}
}
const dataOffset = offset;
const memberDataBlocks = paddedSize(size);
if (memberDataBlocks > buffer.byteLength - dataOffset) {
throw new ArchiveError(`Archive member '${formatArchivePathForError(name)}' is truncated`);
}
offset += memberDataBlocks;
longName = undefined;
longLink = undefined;
localPax = undefined;
const isDirectory = typeFlag === "5" || name.endsWith("/");
const normalizedPath = normalizeArchiveEntryPath(name);
if (!normalizedPath) continue;View on GitHub (pinned to 9690622007)
Solutions
- Re-transfer the archive and verify size/hash against the source.
- Confirm the decompression step completed (gzip/brotli EOF) before buffering for parse.
- Extract with GNU tar first (`tar -xSf`) or repack non-sparsely (`tar -cf`) if you must hand the bytes to this parser.
- Do not truncate/split sparse tar archives by byte count; keep the whole file.
Example fix
// before: buffer may be a partial chunk const buf = chunks[0]; const entries = readTarEntriesFromBuffer(buf, options); // after: only parse the fully received archive const buf = new Uint8Array(await Bun.file(path).arrayBuffer()); const entries = readTarEntriesFromBuffer(buf, options);
Defensive patterns
Strategy: validation
Validate before calling
// require the whole file (sparse tars must be complete) before parsing
const stat = await fs.stat(archivePath);
if (stat.size < (expectedSize ?? 0)) throw new Error(`sparse archive incomplete: ${stat.size} < ${expectedSize}`);
const bytes = new Uint8Array(await Bun.file(archivePath).arrayBuffer());
const entries = readTarEntriesFromBuffer(bytes, { limits }); Try / catch
try {
const entries = readTarEntriesFromBuffer(bytes, { limits });
} catch (err) {
if (err instanceof ArchiveError && /sparse metadata is truncated/.test(err.message)) {
throw new Error('Sparse tar archive is incomplete; re-transfer without truncating');
}
throw err;
} Prevention
- Avoid byte-level truncation of sparse tars (head/dd/split).
- Verify checksums before parsing sparse archives.
- Prefer transferring with resumable protocols (rsync, ranged HTTP) so partial files are detected.
- If only metadata is needed, consider repacking non-sparsely upstream.
When it happens
Trigger: Parsing a tar created with GNU `tar -S` (sparse) where the buffer ends inside the sparse-extension block chain — e.g. a truncated download or single volume of a sparse multi-volume archive.
Common situations: Sparse backups of VM disks, database files, or docker layer blobs (large files with holes) that were incompletely transferred; cutting a sparse tar mid-file with head/dd.
Related errors
- Archive member '${formatArchivePathForError(memberPath)}' is
- Archive member data 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/c5922ac0bc1b69b1.
Report an issue: GitHub.