{"record":{"id":"b1d6d7a0c8d18792","repo":"astrid-runtime/astrid","slug":"invaliddata","errorCode":"InvalidData","errorMessage":"installed executable digest changed: {}","messagePattern":"installed executable digest changed: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/platform_fs/windows/executable.rs","lineNumber":143,"sourceCode":"        \"executable transaction commit\",\n        BoundaryContract::TrustedForCreate,\n        || {\n            for (index, entry) in journal.entries.iter().enumerate() {\n                let live = install_dir.join(&entry.name);\n                let staged = install_dir.join(&entry.staged);\n                if entry.had_live {\n                    replace_file_checked(install_guard, &live, &staged)?;\n                } else {\n                    move_guarded_file(install_guard, &staged, &live)?;\n                }\n                if hash_guarded_regular_file(\n                    install_guard,\n                    &live,\n                    FileContract::Trusted,\n                    BoundaryContract::TrustedForCreate,\n                )? != entry.new_hash\n                {\n                    return Err(io::Error::new(\n                        io::ErrorKind::InvalidData,\n                        format!(\"installed executable digest changed: {}\", live.display()),\n                    ));\n                }\n                test_maybe_interrupt_after_replace(index);\n            }\n\n            // Preserve the prior authenticated executables as conventional backups.\n            // Rollback copies stay independent and live until the journal commit point.\n            for entry in &journal.entries {\n                if let Some(rollback_name) = &entry.rollback {\n                    let rollback = install_dir.join(rollback_name);\n                    let backup = install_dir.join(format!(\"{}.bak\", entry.name));\n                    let staged_backup = stage_transaction_copy(\n                        install_guard,\n                        install_guard,\n                        FileContract::ExactPrivate,\n                        BoundaryContract::TrustedForCreate,","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/platform_fs/windows/executable.rs#L125-L161","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-run the replacement transaction from scratch so the hash is recomputed against current bytes","Check antivirus/EDR logs for quarantine or modification of the installed executable and add an exclusion","Ensure no other updater or process writes to the install directory concurrently","Verify disk health (chkdsk / SMART) if corruption recurs"],"exampleFix":"// before: comparing after the fact fails because AV rewrote the file\n// after: detect and retry the whole transaction once\nmatch replace_executable_set(...) {\n    Err(e) if e.kind() == io::ErrorKind::InvalidData && e.to_string().contains(\"digest changed\") => {\n        // re-stage with fresh bytes and retry once\n    }\n    other => other?,\n}","handlingStrategy":"validation","validationCode":"// Verify the live file digest before committing the transaction\nfn live_digest_matches(live: &std::path::Path, expected_hash: &str) -> bool {\n    std::fs::read(live).map(|bytes| hash(&bytes) == expected_hash).unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match replace_executable_set(...) {\n    Err(e) if e.to_string().starts_with(\"installed executable digest changed\") => {\n        eprintln!(\"Executable changed mid-transaction; re-stage and retry once\");\n    }\n    other => other?,\n}","preventionTips":["Exclude the install directory from AV quarantine/rewrite during updates","Ensure only one updater process runs at a time (single-instance lock)","Never write to installed executables outside the transaction API","Verify disk health if mismatches recur"],"tags":["windows","fs","integrity","hash","update-transaction"],"backgroundTag":"checksum-mismatch","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}