astrid-runtime/astrid · error

capsule source symlink

Error message

capsule source symlink {} does not resolve to a regular file

What it means

Raised by collect_entries when a symlink in the capsule source resolves inside the source root but its target is not a regular file (e.g. a directory, FIFO, or device). Only symlinks to regular files are accepted; they are materialized as regular archive entries (preserving node_modules/.bin semantics).

Solutions

  1. Point the symlink at a regular file, or remove the link and copy the directory contents in as real files.
  2. For directory-style links (version switching), resolve the desired version's files directly into the tree before archiving.
  3. Remove any links to sockets/FIFOs from the capsule directory.

Example fix

// before: symlink to a directory
current -> versions/v2

// after: archive the files themselves
current/** (materialized regular files from versions/v2)
Defensive patterns

Strategy: validation

Validate before calling

fn links_to_regular_file(link: &Path) -> std::io::Result<bool> {
    Ok(std::fs::metadata(link)?.is_file())
}

Type guard

fn is_file_link(link: &Path) -> bool {
    std::fs::symlink_metadata(link).map_or(false, |m| m.file_type().is_symlink())
        && std::fs::metadata(link).map_or(false, |m| m.is_file())
}

Try / catch

if let Err(e) = publish(...) {
    if e.to_string().contains("does not resolve to a regular file") {
        eprintln!("replace directory/special symlinks with real files before publishing");
    }
}

Prevention

When it happens

Trigger: Archiving a capsule containing a symlink to a directory or special file within the tree — e.g. `current -> ./versions/v2/` or a link to a socket — where the resolved fs::metadata().is_file() check fails.

Common situations: Directory links used for version switching inside the capsule; links to pipes/sockets left by tooling; linking a whole vendored directory instead of individual files.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/eee70ed12d544b12. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-capsule-install/src/storage.rs:798

            .to_path_buf();
        let file_type = metadata.file_type();
        if file_type.is_symlink() {
            let resolved = fs::canonicalize(&name).with_context(|| {
                format!("canonicalize capsule source symlink {}", relative.display())
            })?;
            let canonical_root = fs::canonicalize(root)
                .with_context(|| format!("canonicalize capsule source root {}", root.display()))?;
            if !resolved.starts_with(&canonical_root) {
                bail!(
                    "capsule source symlink {} resolves outside source root",
                    relative.display()
                );
            }
            let resolved_metadata = fs::metadata(&resolved).with_context(|| {
                format!("stat capsule source symlink target {}", relative.display())
            })?;
            if !resolved_metadata.is_file() {
                bail!(
                    "capsule source symlink {} does not resolve to a regular file",
                    relative.display()
                );
            }
            // File links are materialized as regular archive entries. This
            // preserves npm's node_modules/.bin links without ever storing a
            // redirect in the durable package.
            entries.push((relative, resolved_metadata));
            continue;
        }
        if file_type.is_dir() {
            if relative.file_name().and_then(|name| name.to_str()) == Some(".git")
                || relative.file_name().and_then(|name| name.to_str()) == Some("target")
            {
                continue;
            }
            // Directory entries are included so empty directories survive
            // archive round-trips; their descendants are sorted recursively.

View on GitHub (pinned to affd8760f4)