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

  1. Re-download or re-copy the archive and compare file sizes/checksums against the source.
  2. Extract the underlying file completely into memory before calling (check that the decompressed stream reached EOF, not just that read() returned).
  3. Validate the tar ends with the expected two zero blocks; a buffer ending mid-member is truncated.
  4. If the source is a multi-volume or split tar, concatenate all parts (cat part1 part2 ...) before parsing.
  5. 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

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


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