can1357/oh-my-pi · error · ArchiveError

Archive contains cyclic or unsupported links

Error message

Archive contains cyclic or unsupported links

What it means

Final safety net of resolvePendingLinks: after the resolution loop, some link entries remain unresolved because they depend on entries that were never settled (mutual dependency not detected as a direct self-cycle, or blocked chains). The archive's link graph cannot be fully resolved, so indexing fails.

Source

Thrown at packages/utils/src/ar/tar.ts:412

		if (!targetIsDirectory) {
			if (pending.kind === "symlink") {
				entry.storage = { type: "link", targetPath: pending.targetPath, resolveTarget: false };
				continue;
			}
			const reason = target ? "unreadable member" : "missing member";
			throw new ArchiveError(
				`Archive hard link '${formatArchivePathForError(entry.path)}' targets ${reason} '${formatArchivePathForError(pending.targetPath)}'`,
			);
		}
		if (pending.kind === "hard link") {
			throw new ArchiveError(
				`Archive hard link '${formatArchivePathForError(entry.path)}' targets directory '${formatArchivePathForError(pending.targetPath)}'`,
			);
		}
		entry.isDirectory = true;
		entry.storage = { type: "link", targetPath: pending.targetPath, resolveTarget: false };
	}
	if (unresolved.size > 0) throw new ArchiveError("Archive contains cyclic or unsupported links");
}

/** Index an already-decompressed tar buffer with bounded GNU, ustar, and PAX handling. */
export function readTarEntriesFromBuffer(buffer: Uint8Array, options: FormatReadOptions): ArchiveIndexEntry[] {
	const { limits } = options;
	assertInMemorySize(buffer.byteLength, limits);
	const entries = new Map<string, ArchiveIndexEntry>();
	const pendingLinks = new Map<ArchiveIndexEntry, PendingTarLink>();
	const addEntry = (entry: ArchiveIndexEntry, pendingLink?: PendingTarLink): void => {
		const existing = entries.get(entry.path);
		const indexed = upsertArchiveEntry(entries, entry);
		if (!indexed) return;
		if (existing) pendingLinks.delete(existing);
		if (pendingLink) pendingLinks.set(indexed, pendingLink);
		assertEntryCount(entries.size, limits);
	};
	let offset = 0;
	let longName: string | undefined;

View on GitHub (pinned to 9690622007)

Solutions

  1. Inspect the archive's link entries (`tar tvf`) and break the dependency chain by replacing one link with a regular file.
  2. Re-pack the archive with dereferencing (`tar -h`) so links become plain files.
  3. Treat as untrusted input in automated pipelines: catch ArchiveError and reject.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await readTar(bytes, opts);
} catch (e) {
  if (e instanceof ArchiveError && e.message === "Archive contains cyclic or unsupported links") {
    throw new Error("Archive link graph unresolvable; re-pack with dereferencing");
  }
  throw e;
}

Prevention

When it happens

Trigger: readTar on an archive whose pendingLinks set is non-empty after the work-queue loop exhausts — e.g. pairs of hard/symlinks that depend on each other via intermediate unresolved entries.

Common situations: Malicious link-graph bombs; archives corrupted so that link targets point at other never-resolvable links; edge cases in round-tripped archives.

Related errors


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