{"record":{"id":"7661846eb0345368","repo":"can1357/oh-my-pi","slug":"invalid-arj-local-header","errorCode":null,"errorMessage":"Invalid ARJ local header","messagePattern":"Invalid ARJ local header","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/arj.ts","lineNumber":262,"sourceCode":"\tif (mainFirstHeaderSize < 30 || mainFirstHeaderSize > main.bodySize || bytes[main.bodyStart + 6] !== 2) {\n\t\tthrow new ArchiveError(\"Invalid ARJ main header\");\n\t}\n\tconst mainFlags = bytes[main.bodyStart + 4]!;\n\tif ((mainFlags & 0x01) !== 0) throw new ArchiveError(\"Encrypted ARJ archives are unsupported\");\n\tif ((mainFlags & 0x04) !== 0) throw new ArchiveError(\"Multi-volume ARJ archives are unsupported\");\n\n\tconst entries: ArchiveIndexEntry[] = [];\n\tlet offset = main.nextOffset;\n\tlet metadataSize = main.metadataSize;\n\tlet parsedCount = 0;\n\tfor (;;) {\n\t\tconst block = parseArjBlock(bytes, offset, options);\n\t\tmetadataSize += block.metadataSize;\n\t\tassertIndexSize(metadataSize, options.limits, \"index\");\n\t\tif (block.isEnd) break;\n\t\tassertEntryCount(++parsedCount, options.limits);\n\t\tconst firstHeaderSize = bytes[block.bodyStart]!;\n\t\tif (firstHeaderSize < 30 || firstHeaderSize > block.bodySize) throw new ArchiveError(\"Invalid ARJ local header\");\n\t\tconst hostOs = bytes[block.bodyStart + 3]!;\n\t\tconst flags = bytes[block.bodyStart + 4]!;\n\t\tconst method = bytes[block.bodyStart + 5]!;\n\t\tconst fileType = bytes[block.bodyStart + 6]!;\n\t\tif ((flags & 0x01) !== 0) throw new ArchiveError(\"Encrypted ARJ members are unsupported\");\n\t\tif ((flags & 0x0c) !== 0) throw new ArchiveError(\"Multi-volume ARJ members are unsupported\");\n\t\tconst packedSize = u32(bytes, block.bodyStart + 12);\n\t\tconst size = u32(bytes, block.bodyStart + 16);\n\t\tconst fileCrc = u32(bytes, block.bodyStart + 20);\n\t\tconst accessMode = u16(bytes, block.bodyStart + 26);\n\t\tconst filename = readCString(\n\t\t\tbytes,\n\t\t\tblock.bodyStart + firstHeaderSize,\n\t\t\tblock.bodyStart + block.bodySize,\n\t\t\t\"filename\",\n\t\t);\n\t\treadCString(bytes, filename.next, block.bodyStart + block.bodySize, \"comment\");\n\t\tassertArchivePathBytes(","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/arj.ts#L244-L280","documentation":"Each ARJ local (file) header must begin with a header-size byte of at least 30 and no larger than the remaining block body. When parseArjBlock returns a non-end block whose first header size byte violates that, the reader concludes the byte stream is not a valid ARJ member sequence — usually meaning the offset walked into garbage, the file is corrupt, or it is not an ARJ archive at all.","triggerScenarios":"readArj walks local headers via parseArjBlock; a block passes with isEnd=false but bytes[block.bodyStart] (basic header size) is <30 or > block.bodySize — truncated download, corrupted middle bytes, or a non-ARJ file misidentified as ARJ (magic check passed by luck).","commonSituations":"Incomplete downloads/uploads; bit-rot on old archives; files renamed to .arj that are actually another format; test fixtures hand-assembled incorrectly.","solutions":["Re-obtain or re-download the archive and verify its checksum against the source of truth.","Validate the file is a real ARJ archive (magic 0x60 0xEA and a sane main header) before parsing; reject early with a clearer error.","Try repairing with `arj y` or an external ARJ tool to see if the header table can be recovered.","If the archive is generated by your own code, dump bytes around the failing offset and fix the writer's header-size field."],"exampleFix":"// before\nconst entries = await readArj(bytes, options); // throws mid-parse on corrupt data\n// after\nif (!looksLikeArj(bytes)) throw new Error(\"Not an ARJ archive\");\nif (!await checksumMatches(bytes, expectedSha256)) throw new Error(\"Archive corrupted in transit\");\nconst entries = await readArj(bytes, options);","handlingStrategy":"validation","validationCode":"import { createHash } from \"node:crypto\";\nfunction assertArchiveIntact(bytes: Uint8Array, expectedSha256?: string): void {\n\tif (!looksLikeArj(bytes)) throw new Error(\"File is not an ARJ archive\");\n\tif (expectedSha256 && createHash(\"sha256\").update(bytes).digest(\"hex\") !== expectedSha256) {\n\t\tthrow new Error(\"Archive corrupted in transit\");\n\t}\n}","typeGuard":"function looksLikeArj(bytes: Uint8Array): boolean {\n\t// ARJ magic: 0x60 0xEA followed by 0x12 0x27 (basic header size 26 + header size field)\n\treturn bytes.length > 4 && bytes[0] === 0x60 && bytes[1] === 0xea;\n}","tryCatchPattern":"try {\n\tentries = await readArj(source, options);\n} catch (e) {\n\tif (e instanceof ArchiveError && e.message === \"Invalid ARJ local header\") {\n\t\tlogger.error(\"ARJ stream corrupt at a local header; verify source checksum\", { path });\n\t\treturn { status: \"corrupt\" };\n\t}\n\tthrow e;\n}","preventionTips":["Always checksum ARJ files (SHA256) at transfer time and verify before parsing.","Validate the ARJ magic and main header before indexing so non-ARJ files fail with a clearer error.","Avoid hand-editing or concatenating ARJ files; regenerate instead."],"tags":["archive","arj","corrupt-file","validation"],"backgroundTag":"corrupt-archive-header","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}