{"record":{"id":"890141a18811318a","repo":"can1357/oh-my-pi","slug":"invalid-ar-archive-long-name-offset-reference","errorCode":null,"errorMessage":"Invalid ar archive long-name offset '${reference}'","messagePattern":"Invalid ar archive long-name offset '(.+?)'","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/unix-ar.ts","lineNumber":133,"sourceCode":"\tif (nameBytes.byteLength === 0) throw new ArchiveError(\"Invalid ar archive empty BSD extended name\");\n\treturn { name: decodeName(nameBytes, limits), byteLength: nameBytes.byteLength };\n}\n\nfunction shortName(rawName: string): string {\n\tif (rawName === \"/\" || rawName === \"//\" || rawName === \"/SYM64/\") return rawName;\n\treturn rawName.endsWith(\"/\") ? rawName.slice(0, -1) : rawName;\n}\n\nfunction resolveLongName(\n\treference: string,\n\ttable: Uint8Array,\n\tlimits: ArchiveLimits,\n): { name: string; byteLength: number } {\n\tconst offsetText = reference.slice(1);\n\tif (!/^\\d+$/.test(offsetText)) throw new ArchiveError(`Invalid ar archive member name '${reference}'`);\n\tconst offset = Number.parseInt(offsetText, 10);\n\tif (!Number.isSafeInteger(offset) || offset < 0 || offset >= table.byteLength) {\n\t\tthrow new ArchiveError(`Invalid ar archive long-name offset '${reference}'`);\n\t}\n\tlet end = offset;\n\twhile (end < table.byteLength && table[end] !== 0 && table[end] !== 0x0a) end++;\n\tif (end === table.byteLength) throw new ArchiveError(`Unterminated ar archive long name at offset ${offset}`);\n\tlet nameEnd = end;\n\tif (table[end] === 0x0a && nameEnd > offset && table[nameEnd - 1] === 0x2f) nameEnd--;\n\tif (nameEnd === offset) throw new ArchiveError(`Empty ar archive long name at offset ${offset}`);\n\tconst nameBytes = table.subarray(offset, nameEnd);\n\treturn { name: decodeName(nameBytes, limits), byteLength: nameBytes.byteLength };\n}\n\nfunction isMetadataName(name: string): boolean {\n\treturn name === \"/\" || name === \"//\" || name === \"/SYM64/\" || name === \"__.SYMDEF\" || name === \"__.SYMDEF SORTED\";\n}\n\nfunction materializeEntries(\n\trecords: RawArMember[],\n\tlongNames: Uint8Array | undefined,","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/unix-ar.ts#L115-L151","documentation":"In resolveLongName, the '/<offset>' reference parsed to a valid number but the offset is not a safe integer, is negative, or points at or beyond the end of the '// ' long-name string table. The offset must index an existing byte in the table for a name to be recovered, so out-of-range references are rejected as 'Invalid ar archive long-name offset'.","triggerScenarios":"Parsing a GNU long-name member whose name is '/999999' while the string table is far smaller; the '// ' table member truncated or missing so table.byteLength is smaller than the referenced offsets; corruption of either the name field or the table.","commonSituations":"Archives where the '// ' table was dropped or truncated by a processing tool (e.g. naive concatenation of archives); off-by-one offset computation in custom writers; fuzzed inputs.","solutions":["Dump the '// ' string-table member and check its byte length against offsets referenced by member names","Regenerate the archive with GNU ar so the table and offsets are rebuilt consistently","Fix offset computation in custom writers (offsets are 0-based byte positions into the table, in emission order)","Check the table member wasn't lost in a concatenate/split operation"],"exampleFix":"// before: offset past end of table\nnameField = '/' + String(table.byteLength) // >= table.byteLength\n// after: offset of where the name was appended\nconst offset = table.byteLength;\ntable = Buffer.concat([table, Buffer.from(name + '/\\n')]);\nnameField = '/' + String(offset);","handlingStrategy":"validation","validationCode":"function longNameOffsetInRange(rawName: string, tableSize: number): boolean {\n  if (!/^\\/\\d+$/.test(rawName)) return false;\n  const off = Number.parseInt(rawName.slice(1), 10);\n  return Number.isSafeInteger(off) && off >= 0 && off < tableSize;\n}\n// parse the '// ' member size first, then validate each '/'-reference","typeGuard":"function isInRangeLongNameOffset(rawName: string, tableSize: number): boolean {\n  const off = Number.parseInt(rawName.slice(1), 10);\n  return Number.isSafeInteger(off) && off >= 0 && off < tableSize;\n}","tryCatchPattern":"try {\n  const files = await archive.list();\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.startsWith('Invalid ar archive long-name offset')) {\n    // string table missing/truncated relative to references — regenerate archive\n  } else throw err;\n}","preventionTips":["Always emit the '// ' table before members that reference it","Recompute offsets whenever table entries are added or removed","Never concatenate ar archives by naive byte appending (offsets break)","Validate offsets against the table size in the writer before finalizing"],"tags":["archive","parsing","ar-format","gnu","bounds-check"],"backgroundTag":"corrupt-archive-header","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}