{"record":{"id":"d61dd536a6ec7ef8","repo":"can1357/oh-my-pi","slug":"invalid-ar-archive-field","errorCode":null,"errorMessage":"Invalid ar archive ${field}","messagePattern":"Invalid ar archive (.+?)","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/unix-ar.ts","lineNumber":63,"sourceCode":"\t}\n}\n\nfunction decodeAsciiField(bytes: Uint8Array, offset: number, length: number): string {\n\tlet end = offset + length;\n\twhile (end > offset && bytes[end - 1] === 0x20) end--;\n\tlet value = \"\";\n\tfor (let index = offset; index < end; index++) {\n\t\tconst byte = bytes[index]!;\n\t\tif (byte < 0x20 || byte > 0x7e) throw new ArchiveError(\"Invalid ar archive header field\");\n\t\tvalue += String.fromCharCode(byte);\n\t}\n\treturn value;\n}\n\nfunction parseOptionalNumber(value: string, radix: 8 | 10, field: string): number | undefined {\n\tif (value === \"\" || value === \"-1\") return undefined;\n\tconst pattern = radix === 8 ? /^[0-7]+$/ : /^\\d+$/;\n\tif (!pattern.test(value)) throw new ArchiveError(`Invalid ar archive ${field}`);\n\tconst parsed = Number.parseInt(value, radix);\n\tif (!Number.isSafeInteger(parsed)) throw new ArchiveError(`Invalid ar archive ${field}`);\n\treturn parsed;\n}\n\nfunction parseRequiredSize(value: string): number {\n\tconst parsed = parseOptionalNumber(value, 10, \"member size\");\n\tif (parsed === undefined) throw new ArchiveError(\"Invalid ar archive member size\");\n\treturn parsed;\n}\n\nfunction parseHeader(header: Uint8Array): {\n\trawName: string;\n\tphysicalSize: number;\n\tmtimeSeconds?: number;\n\tmode?: number;\n\tbsdNameLength?: number;\n} {","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/unix-ar.ts#L45-L81","documentation":"This ArchiveError is thrown by parseOptionalNumber in packages/utils/src/ar/unix-ar.ts when a numeric header field (mode, mtime, uid, gid) contains characters invalid for the expected radix — octal fields must match /^[0-7]+$/, decimal /^\\d+$/, and empty or '-1' (the GNU 'unknown' sentinel) is treated as absent. So this throw means the field is present but malformed. The library throws rather than guessing so corrupt archives are rejected deterministically.","triggerScenarios":"Parsing an ar member whose 6-byte uid, 6-byte gid, 8-byte mode, or 12-byte mtime header slots contain non-digit bytes (non-padding spaces, letters, or negatives other than '-1') via parseHeader, reached through any archive listing/extraction API.","commonSituations":"Hand-edited or truncated .a/.deb archives; broken custom writers that use sign characters or zero-padding instead of space-padding; decimal values written into the octal mode slot; corrupted downloads.","solutions":["Hex-dump the 60-byte member header and verify fields 16-27 (mtime), 28-33 (uid), 34-39 (gid), 40-47 (mode) hold only ASCII digits (0-7 for mode), space-padded","Regenerate the archive with a standard tool (ar, llvm-ar) instead of custom writer code","If you control the writer, space-pad numeric fields to their fixed width","Re-download or restore the archive if corruption in transit is suspected"],"exampleFix":"// before: sign-padded mode field\n'-644    '\n// after: valid octal, space-padded\n'644     '","handlingStrategy":"validation","validationCode":"const dec = new TextDecoder();\nfunction checkNumericField(header: Uint8Array, start: number, len: number, radix: 8 | 10): boolean {\n  const s = dec.decode(header.subarray(start, start + len)).trim();\n  return s === '' || s === '-1' || (radix === 8 ? /^[0-7]+$/.test(s) : /^\\d+$/.test(s));\n}\n// before parsing: mtime, uid, gid, mode slots must all pass\nconst ok = checkNumericField(header, 16, 12, 10) && checkNumericField(header, 28, 6, 10)\n  && checkNumericField(header, 34, 6, 10) && checkNumericField(header, 40, 8, 8);","typeGuard":"function isArNumericField(value: string, radix: 8 | 10): boolean {\n  return value === '' || value === '-1' || (radix === 8 ? /^[0-7]+$/.test(value) : /^\\d+$/.test(value));\n}","tryCatchPattern":"import { ArchiveError } from '@oh-my-pi/pi-utils';\ntry {\n  await archive.list();\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.startsWith('Invalid ar archive')) {\n    // quarantine file, fall back to raw-bytes inspection\n  } else throw err;\n}","preventionTips":["Always space-pad numeric header fields to their fixed widths","Use octal for mode, decimal for mtime/uid/gid","Never digit-fill or sign-pad slots; pad with 0x20","Prefer standard ar/llvm-ar over custom writers","Checksum-verify archives from network sources before parsing"],"tags":["archive","parsing","validation","ar-format"],"backgroundTag":"corrupt-archive-header","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}