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
- Inspect the archive's link entries (`tar tvf`) and break the dependency chain by replacing one link with a regular file.
- Re-pack the archive with dereferencing (`tar -h`) so links become plain files.
- 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
- Re-pack untrusted archives with `tar -h` to eliminate link graphs entirely.
- Keep link chains short and acyclic when generating archives.
- Fail closed on this error — it usually means a link bomb or corruption.
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
- Invalid archive link
- Archive path '${archivePath}' crosses a cyclic symlink
- Archive hard link '${formatArchivePathForError(entry.path)}'
- 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/b68c5fe151fbaace.
Report an issue: GitHub.