can1357/oh-my-pi · error · ArchiveError

Unterminated ar archive long name at offset ${offset}

Error message

Unterminated ar archive long name at offset ${offset}

What it means

resolveLongName scans the string table from the given offset for a terminator (NUL or newline); if it reaches the end of the table without finding one, it throws 'Unterminated ar archive long name at offset <n>'. GNU ar terminates every table entry with '/\n' (or '/' + NUL), so a missing terminator means the '// ' table is malformed or truncated mid-entry.

Source

Thrown at packages/utils/src/ar/unix-ar.ts:137

function shortName(rawName: string): string {
	if (rawName === "/" || rawName === "//" || rawName === "/SYM64/") return rawName;
	return rawName.endsWith("/") ? rawName.slice(0, -1) : rawName;
}

function resolveLongName(
	reference: string,
	table: Uint8Array,
	limits: ArchiveLimits,
): { name: string; byteLength: number } {
	const offsetText = reference.slice(1);
	if (!/^\d+$/.test(offsetText)) throw new ArchiveError(`Invalid ar archive member name '${reference}'`);
	const offset = Number.parseInt(offsetText, 10);
	if (!Number.isSafeInteger(offset) || offset < 0 || offset >= table.byteLength) {
		throw new ArchiveError(`Invalid ar archive long-name offset '${reference}'`);
	}
	let end = offset;
	while (end < table.byteLength && table[end] !== 0 && table[end] !== 0x0a) end++;
	if (end === table.byteLength) throw new ArchiveError(`Unterminated ar archive long name at offset ${offset}`);
	let nameEnd = end;
	if (table[end] === 0x0a && nameEnd > offset && table[nameEnd - 1] === 0x2f) nameEnd--;
	if (nameEnd === offset) throw new ArchiveError(`Empty ar archive long name at offset ${offset}`);
	const nameBytes = table.subarray(offset, nameEnd);
	return { name: decodeName(nameBytes, limits), byteLength: nameBytes.byteLength };
}

function isMetadataName(name: string): boolean {
	return name === "/" || name === "//" || name === "/SYM64/" || name === "__.SYMDEF" || name === "__.SYMDEF SORTED";
}

function materializeEntries(
	records: RawArMember[],
	longNames: Uint8Array | undefined,
	source: ByteSource,
	options: FormatReadOptions,
): ArchiveIndexEntry[] {
	const entries = new Map<string, ArchiveIndexEntry>();

View on GitHub (pinned to 9690622007)

Solutions

  1. Ensure the producer appends '/\n' after every name in the '// ' table, including the last one
  2. Verify the string-table member's size field includes all terminators and pads the table to even size
  3. Regenerate the archive with GNU ar / llvm-ar
  4. Compare the declared table size against actual table bytes to locate the truncation

Example fix

// before: last name not terminated
table = Buffer.concat([table, Buffer.from('lastName')]);
// after: terminate every entry
for (const n of names) table = Buffer.concat([table, Buffer.from(n + '/\n')]);
Defensive patterns

Strategy: try-catch

Validate before calling

function stringTableWellFormed(table: Uint8Array): boolean {
  // every GNU entry ends with '/' followed by \n or NUL
  if (table.byteLength === 0) return true;
  const last = table[table.byteLength - 1];
  return last === 0x0a || last === 0x00;
}
// verify on the '// ' member payload before parsing references

Type guard

function isTerminatedArStringTable(table: Uint8Array): boolean {
  return table.byteLength === 0 || table[table.byteLength - 1] === 0x0a || table[table.byteLength - 1] === 0x00;
}

Try / catch

try {
  const entries = await archive.list();
} catch (err) {
  if (err instanceof ArchiveError && err.message.startsWith('Unterminated ar archive long name')) {
    // truncated string table: re-fetch/rebuild archive; parse offset from message for diagnostics
  } else throw err;
}

Prevention

When it happens

Trigger: Parsing an archive whose string table's final entry lacks its trailing terminator because the '// ' member was truncated by exactly the terminator bytes, or a custom writer forgot to append '/\n' after the last name.

Common situations: Truncated downloads cutting the tail of the archive; custom writers concatenating names without separators; tools that strip trailing newlines from archive members.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/2e2f30a86c8c1c2a. Report an issue: GitHub.