can1357/oh-my-pi · error · ArchiveError

Archive member '${formatArchivePathForError(name)}' is trunc

Error message

Archive member '${formatArchivePathForError(name)}' is truncated

What it means

Thrown after all extended-name/PAX/sparse metadata has been consumed, when the final member's padded data size (memberDataBlocks) extends past the end of the buffer. Unlike the generic truncation check at line 456 (which runs before metadata handling), this check uses the size after PAX 'size' record overrides, so it specifically catches members whose true size — possibly corrected by a PAX header — overruns the buffer. It names the offending member path to pinpoint the corruption.

Source

Thrown at packages/utils/src/ar/tar.ts:515

		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;
		assertArchivePathString(normalizedPath, "member path", limits.maxPathBytes);
		const scaledMtime = mtime * 1000;
		const mtimeMs = mtime !== 0 && Number.isSafeInteger(scaledMtime) ? scaledMtime : undefined;
		if (isDirectory) {
			addEntry({ path: normalizedPath, isDirectory: true, size: 0, mtimeMs, mode });
			continue;
		}
		if (typeFlag === "1" || typeFlag === "2") {
			const kind = typeFlag === "1" ? "hard link" : "symlink";

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-download/re-copy the archive and verify checksum against the publisher.
  2. Check the archive with `tar -tf file.tar` — if GNU tar also complains, the file is corrupt upstream.
  3. Ensure the full decompressed stream was captured (stream not ended early, gunzip not killed by SIGPIPE).
  4. If a PAX-producing tool wrote wrong size records, regenerate the archive with a different tar implementation.
  5. Catch ArchiveError and treat it as a corrupt-archive condition (fail fast; do not retry parsing the same bytes).

Example fix

// before
const buf = await readSomeBytes(fd, partialLength);
return readTarEntriesFromBuffer(buf, options);
// after
const stat = await fs.stat(path);
const expected = manifest['archive.tar'].size;
if (stat.size !== expected) throw new Error(`archive incomplete: ${stat.size}/${expected} bytes`);
return readTarEntriesFromBuffer(new Uint8Array(await Bun.file(path).arrayBuffer()), options);
Defensive patterns

Strategy: try-catch

Validate before calling

// sanity-check that the buffer plausibly terminates with zero blocks before parsing
function hasTerminator(bytes) {
  const tail = bytes.subarray(Math.max(0, bytes.byteLength - 1024));
  return tail.length >= 1024 && tail.every((b) => b === 0);
}
if (!hasTerminator(bytes)) throw new Error('archive appears cut short');

Try / catch

try {
  const entries = readTarEntriesFromBuffer(bytes, { limits });
} catch (err) {
  if (err instanceof ArchiveError) {
    const member = /'([^']+)'/.exec(err.message)?.[1];
    throw new Error(`Corrupt archive: member ${member ?? '?'} data is missing — re-fetch the archive`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling readTarEntriesFromBuffer on a buffer where a member's data (per header size or PAX size record) is not fully present — truncated download, PAX size header disagreeing with actual bytes, or reading one slice of a split archive.

Common situations: Downloads interrupted mid-file; tar files modified by tools that rewrote size fields; extracting only the first volume; build caches that wrote a tar without flushing the final member.

Related errors


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