astrid-runtime/astrid · error

InvalidData

InvalidData

Error message

installed executable digest changed: {}

What it means

At the end of an executable replacement transaction, the library re-reads the live file and compares its digest to the hash recorded when the entry was staged. If the on-disk digest differs, the file changed between staging and commit (or the write was corrupted), so the transaction aborts with InvalidData rather than trusting a modified executable.

Source

Thrown at crates/astrid-core/src/platform_fs/windows/executable.rs:143

        "executable transaction commit",
        BoundaryContract::TrustedForCreate,
        || {
            for (index, entry) in journal.entries.iter().enumerate() {
                let live = install_dir.join(&entry.name);
                let staged = install_dir.join(&entry.staged);
                if entry.had_live {
                    replace_file_checked(install_guard, &live, &staged)?;
                } else {
                    move_guarded_file(install_guard, &staged, &live)?;
                }
                if hash_guarded_regular_file(
                    install_guard,
                    &live,
                    FileContract::Trusted,
                    BoundaryContract::TrustedForCreate,
                )? != entry.new_hash
                {
                    return Err(io::Error::new(
                        io::ErrorKind::InvalidData,
                        format!("installed executable digest changed: {}", live.display()),
                    ));
                }
                test_maybe_interrupt_after_replace(index);
            }

            // Preserve the prior authenticated executables as conventional backups.
            // Rollback copies stay independent and live until the journal commit point.
            for entry in &journal.entries {
                if let Some(rollback_name) = &entry.rollback {
                    let rollback = install_dir.join(rollback_name);
                    let backup = install_dir.join(format!("{}.bak", entry.name));
                    let staged_backup = stage_transaction_copy(
                        install_guard,
                        install_guard,
                        FileContract::ExactPrivate,
                        BoundaryContract::TrustedForCreate,

View on GitHub (pinned to affd8760f4)

Solutions

  1. Re-run the replacement transaction from scratch so the hash is recomputed against current bytes
  2. Check antivirus/EDR logs for quarantine or modification of the installed executable and add an exclusion
  3. Ensure no other updater or process writes to the install directory concurrently
  4. Verify disk health (chkdsk / SMART) if corruption recurs

Example fix

// before: comparing after the fact fails because AV rewrote the file
// after: detect and retry the whole transaction once
match replace_executable_set(...) {
    Err(e) if e.kind() == io::ErrorKind::InvalidData && e.to_string().contains("digest changed") => {
        // re-stage with fresh bytes and retry once
    }
    other => other?,
}
Defensive patterns

Strategy: validation

Validate before calling

// Verify the live file digest before committing the transaction
fn live_digest_matches(live: &std::path::Path, expected_hash: &str) -> bool {
    std::fs::read(live).map(|bytes| hash(&bytes) == expected_hash).unwrap_or(false)
}

Try / catch

match replace_executable_set(...) {
    Err(e) if e.to_string().starts_with("installed executable digest changed") => {
        eprintln!("Executable changed mid-transaction; re-stage and retry once");
    }
    other => other?,
}

Prevention

When it happens

Trigger: finish_executable_transaction (via replace_executable_set) computes the live file hash under the install guard with FileContract::Trusted and it differs from entry.new_hash — e.g. another writer modified the file mid-transaction or the staged bytes did not land intact.

Common situations: Antivirus quarantining or rewriting the freshly installed executable; a second updater process racing the transaction; disk corruption or incomplete flush before commit; user/tooling editing the file between prepare and finish.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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