{"record":{"id":"b68c5fe151fbaace","repo":"can1357/oh-my-pi","slug":"archive-contains-cyclic-or-unsupported-links","errorCode":null,"errorMessage":"Archive contains cyclic or unsupported links","messagePattern":"Archive contains cyclic or unsupported links","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/tar.ts","lineNumber":412,"sourceCode":"\t\tif (!targetIsDirectory) {\n\t\t\tif (pending.kind === \"symlink\") {\n\t\t\t\tentry.storage = { type: \"link\", targetPath: pending.targetPath, resolveTarget: false };\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst reason = target ? \"unreadable member\" : \"missing member\";\n\t\t\tthrow new ArchiveError(\n\t\t\t\t`Archive hard link '${formatArchivePathForError(entry.path)}' targets ${reason} '${formatArchivePathForError(pending.targetPath)}'`,\n\t\t\t);\n\t\t}\n\t\tif (pending.kind === \"hard link\") {\n\t\t\tthrow new ArchiveError(\n\t\t\t\t`Archive hard link '${formatArchivePathForError(entry.path)}' targets directory '${formatArchivePathForError(pending.targetPath)}'`,\n\t\t\t);\n\t\t}\n\t\tentry.isDirectory = true;\n\t\tentry.storage = { type: \"link\", targetPath: pending.targetPath, resolveTarget: false };\n\t}\n\tif (unresolved.size > 0) throw new ArchiveError(\"Archive contains cyclic or unsupported links\");\n}\n\n/** Index an already-decompressed tar buffer with bounded GNU, ustar, and PAX handling. */\nexport function readTarEntriesFromBuffer(buffer: Uint8Array, options: FormatReadOptions): ArchiveIndexEntry[] {\n\tconst { limits } = options;\n\tassertInMemorySize(buffer.byteLength, limits);\n\tconst entries = new Map<string, ArchiveIndexEntry>();\n\tconst pendingLinks = new Map<ArchiveIndexEntry, PendingTarLink>();\n\tconst addEntry = (entry: ArchiveIndexEntry, pendingLink?: PendingTarLink): void => {\n\t\tconst existing = entries.get(entry.path);\n\t\tconst indexed = upsertArchiveEntry(entries, entry);\n\t\tif (!indexed) return;\n\t\tif (existing) pendingLinks.delete(existing);\n\t\tif (pendingLink) pendingLinks.set(indexed, pendingLink);\n\t\tassertEntryCount(entries.size, limits);\n\t};\n\tlet offset = 0;\n\tlet longName: string | undefined;","sourceCodeStart":394,"sourceCodeEnd":430,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/tar.ts#L394-L430","documentation":"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.","triggerScenarios":"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.","commonSituations":"Malicious link-graph bombs; archives corrupted so that link targets point at other never-resolvable links; edge cases in round-tripped archives.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await readTar(bytes, opts);\n} catch (e) {\n  if (e instanceof ArchiveError && e.message === \"Archive contains cyclic or unsupported links\") {\n    throw new Error(\"Archive link graph unresolvable; re-pack with dereferencing\");\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["archive","tar","symlink","cycle","link-resolution"],"backgroundTag":"cyclic-archive-link","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}