astrid-runtime/astrid · error

failed to persist {}: {e}

Error message

failed to persist {}: {e}

What it means

pack builds the .shuttle tar+gzip stream into a temp file and then atomically persists it to the requested output path via tmp.persist(out_path). If the persist (rename) fails, the OS error is wrapped as "failed to persist <path>: {e}". The archive itself was built successfully; only the final move onto the destination path failed.

Source

Thrown at crates/astrid-cli/src/commands/distro/shuttle.rs:153

                        );
                    }
                    header.set_size(metadata.len());
                    header.set_cksum();
                    let mut file = std::fs::File::open(src).with_context(|| {
                        format!("failed to open staged capsule {}", src.display())
                    })?;
                    tar.append_data(&mut header, &entry.path, &mut file)
                        .with_context(|| format!("failed to append {} to shuttle", entry.path))?;
                },
            }
        }

        let encoder = tar.into_inner().context("failed to finish tar stream")?;
        encoder.finish().context("failed to finish gzip stream")?;
    }

    tmp.persist(out_path)
        .map_err(|e| anyhow::anyhow!("failed to persist {}: {e}", out_path.display()))?;
    Ok(())
}

/// Unpack a `.shuttle` at `archive_path` into `dest` (a mirror dir),
/// WITHOUT installing anything.
///
/// Mirrors the hardened defense in the capsule unpacker: absolute paths
/// and `..` traversal are refused, symlinks and hard-links are refused,
/// and each member is size-capped. A malformed or truncated archive
/// yields a clean error.
pub(crate) fn unpack(archive_path: &Path, dest: &Path) -> anyhow::Result<()> {
    std::fs::create_dir_all(dest)
        .with_context(|| format!("failed to create mirror dir {}", dest.display()))?;

    let tar_gz = std::fs::File::open(archive_path)
        .with_context(|| format!("failed to open shuttle: {}", archive_path.display()))?;
    let tar = flate2::read::GzDecoder::new(tar_gz);
    let mut archive = tar::Archive::new(tar);

View on GitHub (pinned to affd8760f4)

Solutions

  1. Create the output directory first (mkdir -p on the parent of out_path) and re-run pack.
  2. Check write permissions on the target directory (and that out_path is not itself a directory).
  3. Ensure out_path is on the same filesystem as the temp dir, or free disk space if the disk is full.
  4. Check for mandatory-access-control denials (audit logs) if permissions look correct but the rename still fails.

Example fix

// before
pack(&input, "out/distro.shuttle")?;   // fails if out/ doesn't exist
// after
std::fs::create_dir_all("out")?;
pack(&input, "out/distro.shuttle")?;
Defensive patterns

Strategy: validation

Validate before calling

let out_path: &Path = out_path.as_ref();
if let Some(parent) = out_path.parent() { std::fs::create_dir_all(parent)?; }
anyhow::ensure!(!out_path.is_dir(), "output path {} is a directory", out_path.display());

Try / catch

match pack(&input, out_path) {
    Err(e) if e.to_string().starts_with("failed to persist") => {
        eprintln!("cannot write {}: check the directory exists and is writable ({e})", out_path.display());
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling pack with an out_path that cannot be written: parent directory missing, no write permission on the directory, out_path is an existing directory, cross-filesystem persist where rename isn't possible, or the temp file was lost before persist.

Common situations: Typing an output path in a nonexistent directory (e.g. out/shuttle/pkg.shuttle when out/ doesn't exist); running from a read-only or user-mismatched directory in CI; disk full or SELinux/AppArmor blocking the rename.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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