astrid-runtime/astrid · error

legacy capsule contains special file

Error message

legacy capsule contains special file {}

What it means

Raised by copy_legacy_tree when a legacy capsule entry is neither a directory nor a regular file (e.g. a FIFO, socket, or device node). The library only copies regular files and directories into canonical legacy archives; anything else is rejected as unsafe/unsupported.

Solutions

  1. Remove or relocate the special file named in the message out of the capsule directory.
  2. Stop any process using the socket/FIFO inside the capsule tree and clean up before re-running the migration.
  3. Restore the capsule from a clean source that contains only regular files and directories.

Example fix

# before: stale socket inside the capsule
capsule/runtime.sock

# after: remove it
rm capsule/runtime.sock
Defensive patterns

Strategy: validation

Validate before calling

fn has_special_files(dir: &Path) -> std::io::Result<bool> {
    Ok(walkdir(dir).any(|e| !e.file_type().is_file() && !e.file_type().is_dir() && !e.file_type().is_symlink()))
}

Type guard

fn is_fs_special(md: &std::fs::Metadata) -> bool {
    !md.is_file() && !md.is_dir() && !md.is_symlink()
}

Try / catch

match migrate(...) {
    Err(e) if e.to_string().contains("special file") => { cleanup_runtime_artifacts(&dir)?; retry(); }
    other => other?,
}

Prevention

When it happens

Trigger: Migrating a legacy capsule tree that contains special filesystem objects — named pipes, unix sockets, device files — at any depth under the capsule directory.

Common situations: Capsule directory previously hosted a running process that left sockets/FIFOs behind; a bad backup/restore produced device nodes; users pointed a capsule dir at a mount point with odd entries.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

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

    fs::create_dir_all(destination)?;
    for (path, metadata) in read_dir_sorted(source)? {
        let relative = path
            .file_name()
            .ok_or_else(|| anyhow::anyhow!("legacy capsule entry has no name"))?;
        let destination = destination.join(relative);
        if metadata.file_type().is_symlink() {
            bail!("legacy capsule contains symlink {}", path.display());
        }
        if metadata.is_dir() {
            copy_legacy_tree(&path, &destination)?;
        } else if metadata.is_file() {
            let name = path.file_name().and_then(|name| name.to_str());
            if matches!(name, Some("meta.json" | "authority.json" | ".env.json")) {
                continue;
            }
            fs::copy(path, destination)?;
        } else {
            bail!("legacy capsule contains special file {}", path.display());
        }
    }
    Ok(())
}

fn manifest_identity(source_dir: &Path) -> anyhow::Result<(String, String)> {
    let manifest = fs::read_to_string(source_dir.join("Capsule.toml"))
        .with_context(|| format!("read capsule manifest from {}", source_dir.display()))?;
    let value: toml::Value = toml::from_str(&manifest).context("parse capsule manifest")?;
    let id = value
        .get("package")
        .and_then(|package| package.get("name"))
        .and_then(toml::Value::as_str)
        .ok_or_else(|| anyhow::anyhow!("Capsule.toml has no package.name"))?;
    let version = value
        .get("package")
        .and_then(|package| package.get("version"))
        .and_then(toml::Value::as_str)

View on GitHub (pinned to affd8760f4)