{"record":{"id":"6211acef281fa247","repo":"can1357/oh-my-pi","slug":"invalid-archive-truncated-data-6211ac","errorCode":null,"errorMessage":"Invalid archive: truncated data","messagePattern":"Invalid archive: truncated data","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/tar.ts","lineNumber":588,"sourceCode":"\t\t\tstorage: { type: \"member\", source: new TarMemberSource(buffer, dataOffset, sparse) },\n\t\t});\n\t}\n\tif (!sawTerminator) throw new ArchiveError(\"Not a valid tar archive: missing terminating zero block\");\n\tresolvePendingLinks(entries, pendingLinks, limits);\n\treturn [...entries.values()];\n}\n\n/** Read and index a tar source after one bounded whole-stream read. */\nexport const readTar: FormatReader = async (source, options) => {\n\tassertInMemorySize(source.size, options.limits);\n\tlet bytes: Uint8Array;\n\ttry {\n\t\tbytes = await readAllBytes(source);\n\t} catch (error) {\n\t\tif (error instanceof ArchiveError) throw error;\n\t\tthrow new ArchiveError(error instanceof Error ? error.message : \"Failed to read tar archive\");\n\t}\n\tif (bytes.byteLength !== source.size) throw new ArchiveError(\"Invalid archive: truncated data\");\n\treturn readTarEntriesFromBuffer(bytes, options);\n};\n\n/** Detect a tar header, including legacy pre-ustar archives, by its checksum. */\nexport function sniffTar(bytes: Uint8Array): boolean {\n\tif (bytes.byteLength < BLOCK_SIZE) return false;\n\tif (isZeroBlock(bytes, 0)) return true;\n\ttry {\n\t\tif (readTarString(bytes, NAME_OFFSET, NAME_LENGTH).length === 0) return false;\n\t\tconst size = readTarSize(bytes, SIZE_OFFSET);\n\t\treturn Number.isSafeInteger(paddedSize(size)) && checksumMatches(bytes, 0);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction writeField(target: Uint8Array, offset: number, length: number, value: Uint8Array): void {\n\tif (value.byteLength > length) throw new ArchiveError(\"Tar header field is too long\");","sourceCodeStart":570,"sourceCodeEnd":606,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/tar.ts#L570-L606","documentation":"After reading all bytes, readTar compares the byte count to the declared source size. A mismatch means the source delivered fewer bytes than promised — the archive is truncated. This is a hard structural failure because tar parsing cannot know how much trailing data was lost.","triggerScenarios":"Providing a tar source whose `size` metadata says N but whose stream yields fewer than N bytes — a size header taken from an HTTP Content-Length while the body was cut short, a stat() size on a file being concurrently truncated, or a source that reports pre-decompression size.","commonSituations":"Interrupted downloads with a Content-Length-driven progress bar, files still being written while read, mismatched size metadata in custom sources, gz not fully decompressed before being handed to readTar with the compressed size.","solutions":["Re-fetch/re-copy the archive and confirm the final byte length matches the expected size","Do not pass a size value larger than the actual available data; compute size from the bytes you actually have","Wait for the producing process/stream to finish (check file size stability or completion event) before reading","If the size came from Content-Length, compare against the actual received length and treat mismatch as a failed download"],"exampleFix":"// before: trusting a potentially stale stat size\nawait readTar({ size: stat.size, read: () => stream });\n// after: size = what was actually read\nconst bytes = new Uint8Array(await new Response(stream).arrayBuffer());\nawait readTar({ size: bytes.byteLength, read: async () => bytes });","handlingStrategy":"validation","validationCode":"if (bytes.byteLength !== declaredSize) {\n  throw new Error(`archive incomplete: got ${bytes.byteLength} of ${declaredSize} bytes`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await readTar(source, options);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message === \"Invalid archive: truncated data\") {\n    // re-download / wait for producer to finish\n  }\n  throw err;\n}","preventionTips":["Always derive source.size from the bytes actually received, not from headers like Content-Length","Only read archives after the producing process/stream has signaled completion","Compare downloaded size against expected checksum/size before parsing"],"tags":["tar","archive","truncation","size-mismatch"],"backgroundTag":"archive-truncated","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}