{"record":{"id":"f0952f38d6ec28b5","repo":"can1357/oh-my-pi","slug":"invalid-xz-stream-missing-block-header","errorCode":null,"errorMessage":"Invalid XZ stream: missing block header","messagePattern":"Invalid XZ stream: missing block header","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/codecs/xz.ts","lineNumber":430,"sourceCode":"\tif (checkId === 0) return;\n\tif (checkId === 1) {\n\t\tif (read32LE(expected, 0) !== crc32(output)) throw new ArchiveError(\"Invalid XZ stream: block CRC32 mismatch\");\n\t\treturn;\n\t}\n\tif (checkId === 4) {\n\t\tconst actual = crc64(output);\n\t\tlet stored = 0n;\n\t\tfor (let index = 0; index < 8; index++) stored |= BigInt(expected[index]!) << BigInt(index * 8);\n\t\tif (actual !== stored) throw new ArchiveError(\"Invalid XZ stream: block CRC64 mismatch\");\n\t\treturn;\n\t}\n\tconst actual = new Uint8Array(new Bun.CryptoHasher(\"sha256\").update(output).digest());\n\tif (!equalBytes(actual, expected)) throw new ArchiveError(\"Invalid XZ stream: block SHA-256 mismatch\");\n}\n\nasync function decodeBlock(bytes: Uint8Array, offset: number, record: XzRecord, checkId: number): Promise<Uint8Array> {\n\tif (offset >= bytes.byteLength || bytes[offset] === 0)\n\t\tthrow new ArchiveError(\"Invalid XZ stream: missing block header\");\n\tconst headerSize = (bytes[offset]! + 1) * 4;\n\tif (offset + headerSize > bytes.byteLength || headerSize < 8)\n\t\tthrow new ArchiveError(\"Invalid XZ stream: truncated block header\");\n\tif (crc32(bytes.subarray(offset, offset + headerSize - 4)) !== read32LE(bytes, offset + headerSize - 4))\n\t\tthrow new ArchiveError(\"Invalid XZ stream: block header CRC32 mismatch\");\n\tconst cursor: Cursor = { bytes, pos: offset + 1, limit: offset + headerSize - 4 };\n\tconst flags = bytes[cursor.pos++]!;\n\tif ((flags & 0x3c) !== 0) throw new ArchiveError(\"Unsupported XZ block flags\");\n\tconst filterCount = (flags & 3) + 1;\n\tconst declaredCompressed = (flags & 0x40) !== 0 ? readVarInt(cursor) : undefined;\n\tconst declaredUncompressed = (flags & 0x80) !== 0 ? readVarInt(cursor) : undefined;\n\tconst filters: XzFilter[] = [];\n\tfor (let index = 0; index < filterCount; index++) {\n\t\tconst id = readVarInt(cursor);\n\t\tconst propertySize = readVarInt(cursor);\n\t\tif (propertySize > cursor.limit - cursor.pos)\n\t\t\tthrow new ArchiveError(\"Invalid XZ stream: truncated filter properties\");\n\t\tfilters.push({ id, properties: bytes.slice(cursor.pos, cursor.pos + propertySize) });","sourceCodeStart":412,"sourceCodeEnd":448,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/codecs/xz.ts#L412-L448","documentation":"When decoding an XZ block the library expects to find a valid block header at the current offset: the offset must be inside the buffer and the first byte (header size indicator) must be non-zero (0 is the special 'no block' marker for padding). It throws when the offset is past the end of the buffer or the byte there is 0, meaning the block header is absent from where the index said it would be.","triggerScenarios":"Decoding a truncated XZ stream (buffer ends before the block header); an XZ stream where padding/misalignment zeros appear where a block header should start; a corrupted index pointing at the wrong offset.","commonSituations":"Files cut off mid-transfer; archives concatenated or padded incorrectly; storage corruption zeroing out header bytes.","solutions":["Re-obtain the complete file — check its size against the source and re-download if truncated","Verify externally with `xz -t`; if it fails there too the file is damaged","Ensure you're passing the entire .xz file to the decoder (not a partial slice/stream chunk)","Reject zero-padded or concatenated non-standard files; split them properly before decoding"],"exampleFix":"// before: decoding a partial chunk\nconst buf = new Uint8Array(await firstChunkOnly());\nawait decodeXz(buf);\n// after: read the whole file\nconst buf = new Uint8Array(await Bun.file('archive.xz').arrayBuffer());\nawait decodeXz(buf);","handlingStrategy":"validation","validationCode":"// Ensure the whole file is present before decoding\nconst stat = await Bun.file('archive.xz').stat?.() ?? null;\nconst bytes = new Uint8Array(await Bun.file('archive.xz').arrayBuffer());\nif (bytes.byteLength < 32) throw new Error('File too small to be a complete XZ stream');","typeGuard":"null","tryCatchPattern":"try {\n  return await decodeXz(bytes);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('missing block header')) {\n    throw new Error('XZ stream is truncated or contains unexpected padding; re-obtain the complete file');\n  }\n  throw err;\n}","preventionTips":["Read the entire .xz file into the decoder; never decode partial stream chunks","Verify expected file size before decode when the source provides one","Avoid concatenating or padding XZ files ad hoc — use proper multi-stream formats","Handle downloads transactionally: write to temp, verify, then rename"],"tags":["xz","archive","truncated-input","corrupt-input"],"backgroundTag":"truncated-archive","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}