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
- List the archive's links (`tar tvf`) and locate the self-referential hard-link pair named in the message.
- Re-create the archive without the cyclic hard link, replacing it with a regular file copy.
- 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
- Never create hard links whose target chain loops back to the link itself.
- Sanitize untrusted archives (e.g. with bsdtar re-pack) before indexing.
- Treat hard-link cycles as malicious input and reject outright.
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
- Archive hard link '${formatArchivePathForError(entry.path)}'
- Archive hard link '${formatArchivePathForError(entry.path)}'
- Archive contains cyclic or unsupported links
- Invalid tar octal value: ${value}
- Truncated embedded addon archive entry: ${filename}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/9150e6c866a027a4.
Report an issue: GitHub.