can1357/oh-my-pi · error · ArchiveError
Archive symlink '${formatArchivePathForError(memberPath)}' c
Error message
Archive symlink '${formatArchivePathForError(memberPath)}' cannot be materialized from target '${formatArchivePathForError(targetPath)}' What it means
Canonical error for a symlink entry inside an archive whose target path cannot be resolved to a real member, or cannot otherwise be materialized. `throwUnreadableArchiveLink` is raised from `readFile` when following an archive symlink/alias hits a dead end, and reports both the symlink's path and the unresolved target (packages/utils/src/ar/reader.ts:15).
Source
Thrown at packages/utils/src/ar/reader.ts:15
import { ensureParentDirectories, resolveArchiveLinkPath, upsertArchiveEntry } from "./entries";
import { ArchiveError } from "./error";
import { type ArchiveLimits, assertArchiveMemberSize, DEFAULT_ARCHIVE_LIMITS } from "./limits";
import { formatArchivePathForError, normalizeArchiveLookupPath } from "./paths";
import type {
ArchiveDirectoryEntry,
ArchiveFormat,
ArchiveIndexEntry,
ArchiveNode,
ExtractedArchiveFile,
} from "./types";
/** Raise the canonical error for a symlink whose target cannot be materialized. */
export function throwUnreadableArchiveLink(targetPath: string, memberPath: string): never {
throw new ArchiveError(
`Archive symlink '${formatArchivePathForError(memberPath)}' cannot be materialized from target '${formatArchivePathForError(targetPath)}'`,
);
}
/**
* An indexed, read-only view over a single archive. Member payloads stay
* lazy behind their format's `MemberSource`; symlink aliases are traversed
* lazily so N files behind M directory aliases never inflate the index to
* N×M entries during listing.
*/
export class ArchiveReader {
readonly format: ArchiveFormat;
readonly limits: ArchiveLimits;
#entries = new Map<string, ArchiveIndexEntry>();
constructor(format: ArchiveFormat, entries: ArchiveIndexEntry[], limits: ArchiveLimits = DEFAULT_ARCHIVE_LIMITS) {
this.format = format;
this.limits = limits;View on GitHub (pinned to 9690622007)
Solutions
- Inspect the archive listing (`reader.allEntries()`/`listDirectory`) and check whether the link's target entry actually exists; re-create the archive with the target included or the link removed.
- Fix dangling symlinks in the source directory before archiving (e.g. `find . -xtype l -delete` or repoint them).
- If the target legitimately lives outside the archive, copy the real file into the archive rather than linking.
- Catch this ArchiveError around readFile and treat dead links as skippable members, logging the memberPath/targetPath from the message.
Example fix
// before: throws on a dangling archive symlink
const file = await reader.readFile('lib/current.so');
// after: guard against unmaterializable links
const entry = reader.allEntries().find(e => e.path === 'lib/current.so');
if (entry?.isSymlink) {
const target = entry.linkTarget ?? '';
if (!reader.allEntries().some(e => e.path === target)) {
throw new Error(`Skipping broken link: ${entry.path} -> ${target}`);
}
}
const file = await reader.readFile('lib/current.so'); Defensive patterns
Strategy: try-catch
Validate before calling
// Verify a symlink's target exists among archive entries before reading it
const entries = reader.allEntries();
const link = entries.find(e => e.path === memberPath);
if (link?.isSymlink && !entries.some(e => e.path === link.linkTarget)) {
throw new Error(`Dangling archive symlink: ${memberPath} -> ${link.linkTarget}`);
} Type guard
function isReadableSymlink(
entries: { path: string; isDirectory: boolean; isSymlink?: boolean; linkTarget?: string }[],
memberPath: string,
): boolean {
const link = entries.find(e => e.path === memberPath);
if (!link?.isSymlink) return true;
return entries.some(e => e.path === link.linkTarget && !e.isDirectory);
} Try / catch
try {
return await reader.readFile(memberPath);
} catch (err) {
if (err instanceof ArchiveError && err.message.startsWith("Archive symlink '")) {
logger.warn('Skipping unmaterializable archive symlink', { memberPath });
return null; // treat as skippable member
}
throw err;
} Prevention
- Remove or fix dangling symlinks in source directories before archiving (`find . -xtype l`).
- Prefer storing real file copies over symlinks in distributed archives.
- Avoid absolute or outside-root symlink targets when creating archives.
- List and audit symlink entries (path + target) when ingesting third-party archives.
When it happens
Trigger: Calling `reader.readFile(path)` where `path` resolves (through one or more symlinks, up to limits.maxLinkDepth) to a target that does not exist among the archive entries, or the link chain terminates without a concrete file entry.
Common situations: Archives created from directories containing dangling symlinks (broken dev environments, node_modules links, /etc/alternatives-style links); tar/zip pickled with absolute symlink targets that don't exist inside the archive; link chains pointing outside the archive root.
Related errors
- Invalid CPIO archive: symlink '${recordPath}' has an invalid
- Archive path '${archivePath}' crosses a cyclic symlink
- Invalid LZH symbolic link '${header.path}'
- Archive symlink escapes extraction dir: ${link.path} -> ${li
- Unsupported compressed RAR4 symlink '${path}'
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/bd4a64760f78446b.
Report an issue: GitHub.