can1357/oh-my-pi · error · ArchiveError

Archive hard link '${formatArchivePathForError(entry.path)}'

Error message

Archive hard link '${formatArchivePathForError(entry.path)}' has a cyclic target '${formatArchivePathForError(pending.targetPath)}'

What it means

Thrown when a tar hard link's target chain resolves back to the hard-link entry itself, i.e. the link is cyclic (directly or through other links). Hard links must point at a concrete file member; a cycle can never resolve, so indexing aborts.

Source

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

				if (!(error instanceof ArchiveError)) throw new ArchiveError("Invalid archive link");
			}
			if (targetPath !== pending.targetPath) blocker = findUnresolvedBlocker(targetPath);
		}
		if (blocker !== null && blocker !== entry) {
			const waiting = dependents.get(blocker);
			if (waiting) waiting.push(entry);
			else dependents.set(blocker, [entry]);
			continue;
		}
		unresolved.delete(entry);
		const settled = dependents.get(entry);
		if (settled) {
			dependents.delete(entry);
			queue.push(...settled);
		}
		if (blocker === entry) {
			if (pending.kind === "hard link") {
				throw new ArchiveError(
					`Archive hard link '${formatArchivePathForError(entry.path)}' has a cyclic target '${formatArchivePathForError(pending.targetPath)}'`,
				);
			}
			entry.storage = { type: "link", targetPath: pending.targetPath, resolveTarget: false };
			continue;
		}
		const target = entries.get(targetPath);
		if (target?.storage && !target.isDirectory && !unresolved.has(target)) {
			entry.size = target.size;
			entry.storage = target.storage;
			continue;
		}
		const targetIsDirectory = targetPath === "" || target?.isDirectory === true || directoryPrefixes.has(targetPath);
		if (!targetIsDirectory) {
			if (pending.kind === "symlink") {
				entry.storage = { type: "link", targetPath: pending.targetPath, resolveTarget: false };
				continue;
			}

View on GitHub (pinned to 9690622007)

Solutions

  1. List the archive's links (`tar tvf`) and locate the self-referential hard-link pair named in the message.
  2. Re-create the archive without the cyclic hard link, replacing it with a regular file copy.
  3. Reject the archive as malicious/corrupt in automated pipelines — catch ArchiveError and fail closed.
Defensive patterns

Strategy: validation

Validate before calling

// Pre-scan hardlink entries for self-referential targets before indexing
// (entry.path === resolved linkname indicates a cycle)

Try / catch

try {
  await readTar(bytes, opts);
} catch (e) {
  if (e instanceof ArchiveError && e.message.includes("cyclic target")) {
    throw new Error("Refusing archive: hard-link cycle (possible tampering)");
  }
  throw e;
}

Prevention

When it happens

Trigger: readTar on an archive containing typeflag-'1' entries where entry A hard-links to B and B (or a chain) links back to A; the pending-link resolver detects blocker === entry for a hard link.

Common situations: Maliciously crafted archives (link-cycle bombs), archives corrupted so linkname fields point back at themselves, or round-trips through tools that mishardlink.

Related errors


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