{"record":{"id":"1e1612e54243588d","repo":"astrid-runtime/astrid","slug":"alreadyexists","errorCode":"AlreadyExists","errorMessage":"an executable replacement transaction is already pending","messagePattern":"an executable replacement transaction is already pending","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/astrid-core/src/platform_fs/windows/executable.rs","lineNumber":267,"sourceCode":"            rollback,\n            legacy_displaced: None,\n            had_live,\n            old_hash,\n            new_hash,\n        });\n    }\n    cleanup.disarm();\n    Ok(journal)\n}\n\npub(super) fn write_transaction_journal(\n    install_dir: &Path,\n    journal: &ExecutableTransaction,\n    guard: &TrustedPathGuard,\n) -> io::Result<()> {\n    let journal_path = install_dir.join(TRANSACTION_JOURNAL);\n    if guarded_file_exists(guard, &journal_path)? {\n        return Err(io::Error::new(\n            io::ErrorKind::AlreadyExists,\n            \"an executable replacement transaction is already pending\",\n        ));\n    }\n    let bytes = serde_json::to_vec(journal)\n        .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;\n    let (staged, journal_file) =\n        stage_unique_bytes_retained(guard, install_dir, &bytes, \"astrid-update-journal\")?;\n    #[cfg(test)]\n    if let Err(error) = super::io::test_maybe_fail_journal_rename() {\n        drop(journal_file);\n        let _ = remove_guarded_file(guard, &staged);\n        return Err(error);\n    }\n    if let Err(error) = move_guarded_file(guard, &staged, &journal_path) {\n        drop(journal_file);\n        let _ = remove_guarded_file(guard, &staged);\n        return Err(error);","sourceCodeStart":249,"sourceCodeEnd":285,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/platform_fs/windows/executable.rs#L249-L285","documentation":"Executable replacement is a journal-based transaction: before writing a new journal, write_transaction_journal checks whether TRANSACTION_JOURNAL already exists in the install directory. If it does, a previous replacement transaction is still pending (in progress, interrupted, or not recovered), and starting another would corrupt the transaction protocol, so it fails with AlreadyExists.","triggerScenarios":"Calling replace_executable_set when install_dir already contains a transaction journal file — from a concurrently running update or from a previous crashed/killed update that was never recovered via recover_executable_transaction_locked.","commonSituations":"Two updater instances racing on the same install; a prior update crashed before commit/rollback and left the journal on disk; recovery logic never ran after a power loss mid-update.","solutions":["Check for another running updater instance and let it finish before retrying","Run the recovery path (recover_executable_transaction_locked) to commit or roll back the pending journal, then retry","Inspect the journal JSON in install_dir to decide whether to complete or roll back manually","Only delete the journal file if you have confirmed the transaction is fully applied or safely discardable"],"exampleFix":"// before: blind retry\nreplace_executable_set(...)?;\n// after: recover a pending journal before starting a new transaction\nif install_dir.join(TRANSACTION_JOURNAL).exists() {\n    recover_executable_transaction_locked(&guard, &install_dir)?; // commit or roll back\n}\nreplace_executable_set(...)?;","handlingStrategy":"try-catch","validationCode":"// Detect a pending journal before starting a replacement\nfn transaction_pending(install_dir: &std::path::Path) -> bool {\n    install_dir.join(\"astrid-update-journal\").exists()\n        || install_dir.join(TRANSACTION_JOURNAL).exists()\n}","typeGuard":null,"tryCatchPattern":"match replace_executable_set(...) {\n    Err(e) if e.kind() == io::ErrorKind::AlreadyExists\n        && e.to_string().contains(\"already pending\") => {\n        recover_executable_transaction_locked(&guard, &install_dir)?; // commit/roll back, then retry\n    }\n    other => other?,\n}","preventionTips":["Serialize updates with a process/machine-wide single-instance lock","Always run recovery after a crashed or interrupted update","Alert on leftover journal files in the install directory"],"tags":["windows","fs","transaction","concurrency","update-transaction"],"backgroundTag":"file-already-exists","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"}