astrid-runtime/astrid · error

failed to replace signed capsule archive: {error}

Error message

failed to replace signed capsule archive: {error}

What it means

rewrite_with_provenance rebuilds a signed capsule archive into a staged temporary file and then atomically persists it over the original archive path with persist(). If the OS refuses to replace the target (persist fails), the library wraps the persist error with this message so the caller knows the signed archive was built but could not be swapped into place.

Source

Thrown at crates/astrid-build/src/artifact.rs:430

                0o755
            } else {
                0o644
            };
            let mut header = deterministic_header(entry.size(), mode, entry_type);
            target
                .append_data(&mut header, &path, &mut entry)
                .with_context(|| format!("failed to copy capsule entry '{path}'"))?;
        }
        let mut header =
            deterministic_header(envelope.len() as u64, 0o644, tar::EntryType::Regular);
        target.append_data(&mut header, PROVENANCE_FILE, envelope)?;
        let encoder = target.into_inner()?;
        encoder.finish()?;
    }
    staged.as_file_mut().sync_all()?;
    staged
        .persist(archive_path)
        .map_err(|error| anyhow::anyhow!("failed to replace signed capsule archive: {error}"))?;
    Ok(())
}

/// Construct a metadata-stable GNU tar header for synthesized or rewritten
/// entries. Content and executable intent remain significant; host ownership
/// and wall-clock timestamps do not.
fn deterministic_header(size: u64, mode: u32, entry_type: tar::EntryType) -> tar::Header {
    let mut header = tar::Header::new_gnu();
    header.set_size(size);
    header.set_mode(mode);
    header.set_uid(0);
    header.set_gid(0);
    header.set_mtime(tar::DETERMINISTIC_TIMESTAMP);
    header.set_entry_type(entry_type);
    header.set_cksum();
    header
}

View on GitHub (pinned to affd8760f4)

Solutions

  1. Check the wrapped inner error: fix permissions on archive_path and its parent directory (writable by the build user).
  2. Close other processes holding the archive open (editors, AV scanners, prior build steps) and retry.
  3. Ensure the staged temp file and archive_path are on the same filesystem so the atomic rename can succeed.
  4. Remove read-only/immutable flags from the existing archive before signing.

Example fix

// before: destination read-only, persist fails
// -rw-r--r-- root capsule.tar.zst (owned by root, build runs as ci)
// after: ensure ownership/permissions before signing
chown ci:ci capsule.tar.zst && chmod u+w capsule.tar.zst
cargo build --release
Defensive patterns

Strategy: fallback

Validate before calling

// Pre-flight: destination must be writable and not read-only before signing
let meta = std::fs::metadata(archive_path)?;
if meta.permissions().readonly() {
    let mut perms = meta.permissions();
    perms.set_permissions(perms.mode() & !0o444 | 0o200);
    std::fs::set_permissions(archive_path, perms)?;
}

Try / catch

match sign_archive(&archive_path, &key).await {
    Ok(()) => {},
    Err(e) if e.to_string().contains("failed to replace signed capsule archive") => {
        // fallback: write to a sibling path so the build still produces the artifact
        let alt = archive_path.with_extension("tar.zst.new");
        rewrite_with_provenance(&alt, provenance).await?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling sign_archive (which calls rewrite_with_provenance) when tempfile::persist fails to rename the staged file onto archive_path — target path locked/open by another process, permission denied on the destination or its directory, cross-filesystem rename, or destination removed/immutable.

Common situations: Antivirus, backup, or another build process holding the archive open (common on Windows); read-only output directory or CI cache permissions; archive_path on a different mount than the staging directory; the file marked read-only from a previous artifact sync.

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