astrid-runtime/astrid · error

failed to persist {}: {e}

Error message

failed to persist {}: {e}

What it means

Thrown by `write_lock` when the atomic persist of Distro.lock fails. The lock file is staged in a `tempfile::NamedTempFile` next to the target and then `tmp.persist(path)` moves it into place; persist can fail (e.g. the target already exists via PersistError, or cross-device rename). The message includes the target path and the underlying {e}.

Source

Thrown at crates/astrid-cli/src/commands/distro/lock.rs:92

    };
    let lock: DistroLock = toml::from_str(&content).context("failed to parse Distro.lock")?;
    Ok(Some(lock))
}

/// Write a lockfile to disk. Uses atomic write (temp + rename) to avoid
/// partial writes on crash.
pub(crate) fn write_lock(path: &Path, lock: &DistroLock) -> anyhow::Result<()> {
    let content = toml::to_string_pretty(lock).context("failed to serialize Distro.lock")?;
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)
            .with_context(|| format!("failed to create {}", parent.display()))?;
    }
    let mut tmp = tempfile::NamedTempFile::new_in(path.parent().unwrap_or(Path::new(".")))
        .context("failed to create temp file for Distro.lock")?;
    std::io::Write::write_all(&mut tmp, content.as_bytes())
        .context("failed to write Distro.lock staging")?;
    tmp.persist(path)
        .map_err(|e| anyhow::anyhow!("failed to persist {}: {e}", path.display()))?;
    Ok(())
}

/// Convert a parsed lock payload to the bounded daemon control-plane shape.
pub(crate) fn to_provenance(lock: &DistroLock) -> DistroProvenance {
    DistroProvenance {
        schema_version: lock.schema_version,
        distro_id: lock.distro.id.clone(),
        distro_version: lock.distro.version.clone(),
        resolved_at: lock.distro.resolved_at.clone(),
        capsules: lock
            .capsules
            .iter()
            .map(|capsule| DistroCapsuleProvenance {
                name: capsule.name.clone(),
                version: capsule.version.clone(),
                source: capsule.source.clone(),
                hash: capsule.hash.clone(),

View on GitHub (pinned to affd8760f4)

Solutions

  1. Check the wrapped {e} for the concrete persist error and fix that condition (permissions, existing file, full disk).
  2. Re-run the lock generation after stopping concurrent `astrid distro` operations to avoid persist races.
  3. Ensure the directory containing Distro.lock is writable: `chmod u+w <distro_dir>`.
  4. If the target exists and is stale, remove `Distro.lock` and regenerate it.

Example fix

// before
let path = Path::new("Distro.lock");
write_lock(path, &content)?; // temp may land in "." on different fs
// after
let path = Path::new("/home/u/proj/Distro.lock");
std::fs::create_dir_all(path.parent().unwrap())?;
write_lock(path, &content)?;
Defensive patterns

Strategy: try-catch

Validate before calling

let dir = path.parent().unwrap_or(Path::new("."));
let meta = std::fs::metadata(dir)
    .map_err(|e| anyhow!("lock target dir {} unusable: {e}", dir.display()))?;
if meta.permissions().readonly() {
    return Err(anyhow!("lock target dir {} is read-only", dir.display()));
}

Try / catch

match write_lock(path, &content) {
    Err(e) if e.to_string().starts_with("failed to persist") => {
        // persist raced or fs issue: retry once after removing a stale target
        let _ = std::fs::remove_file(path);
        write_lock(path, &content)
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling write_lock (directly or via persist_lock_if_earned after a lock-regeneration flow) where persist fails: target file locked/replaced concurrently, target directory on a different filesystem than the temp file, or permission problems moving onto the destination.

Common situations: Two processes regenerating Distro.lock simultaneously and racing persist; the distro directory mounted read-only; tempfile created in `.` fallback (different fs from target) causing cross-device rename errors.

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/28ced88431257c19. Report an issue: GitHub.