astrid-runtime/astrid · error

capsule archive path is not UTF-8

Error message

capsule archive path is not UTF-8

What it means

canonical_archive_for_source walks the source archive's entries to build a canonical file list, and requires every member path to be representable as UTF-8 so it can normalize separators ('\' -> '/') and detect duplicates deterministically. An archive entry whose path bytes are not valid UTF-8 triggers this error.

Solutions

  1. Find the offending entry (list archive contents) and rename it to a UTF-8 name, then rebuild the archive.
  2. Re-create the archive with a UTF-8 locale (LC_ALL=C.UTF-8) using tar/bsdtar.
  3. Exclude non-UTF-8 files from the capsule source directory before packaging.
  4. Convert filenames with convmv (convmv -f latin1 -t utf8 -r --notest <dir>) and repackage.

Example fix

// before
tar -cf capsule.tar ./src  # built under a non-UTF-8 locale
// after
LC_ALL=C.UTF-8 tar -cf capsule.tar ./src
Defensive patterns

Strategy: validation

Validate before calling

fn all_paths_utf8(archive: &std::path::Path) -> anyhow::Result<()> {
    let f = std::fs::File::open(archive)?;
    let mut tar = tar::Archive::new(std::io::BufReader::new(f));
    for entry in tar.entries()? {
        let entry = entry?;
        let p = entry.path()?.to_path_buf();
        if p.to_str().is_none() {
            anyhow::bail!("non-UTF-8 archive path: {}", p.display());
        }
    }
    Ok(())
}

Prevention

When it happens

Trigger: archive_digest_for_source is given a capsule archive (tar) that contains an entry with non-UTF-8 filename bytes — typically archives created on filesystems with non-UTF-8 encodings or by tools writing raw byte paths.

Common situations: Packing a capsule on a legacy-locale Linux (e.g. Latin-1 filenames) or Windows tool emitting non-UTF-8 names; third-party archives downloaded from the internet containing odd entry names.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at crates/astrid-capsule-install/src/source_digest.rs:51

    let staging = tempfile::tempdir().context("create source digest staging directory")?;
    let file = fs::File::open(source)
        .with_context(|| format!("open capsule archive {}", source.display()))?;
    let decoder = flate2::read::GzDecoder::new(file);
    let mut archive = tar::Archive::new(decoder);
    let mut names = BTreeSet::new();
    for entry in archive.entries().context("read capsule archive entries")? {
        let mut entry = entry.context("read capsule archive entry")?;
        let path = entry.path().context("read capsule archive path")?;
        if path.is_absolute()
            || path
                .components()
                .any(|component| matches!(component, std::path::Component::ParentDir))
        {
            bail!("capsule archive contains an unsafe path {}", path.display());
        }
        let name = path
            .to_str()
            .ok_or_else(|| anyhow::anyhow!("capsule archive path is not UTF-8"))?
            .replace('\\', "/");
        if !names.insert(name.clone()) {
            bail!("capsule archive contains duplicate path {name}");
        }
        let entry_type = entry.header().entry_type();
        if !entry_type.is_dir() && !entry_type.is_file() {
            bail!("capsule archive contains a link or special file {name}");
        }
        let destination = staging.path().join(&path);
        if entry_type.is_dir() {
            fs::create_dir_all(&destination)
                .with_context(|| format!("create capsule archive directory {name}"))?;
            continue;
        }
        if let Some(parent) = destination.parent() {
            fs::create_dir_all(parent)
                .with_context(|| format!("create capsule archive parent for {name}"))?;
        }

View on GitHub (pinned to affd8760f4)