{"record":{"id":"aee3e2a04ecc37a6","repo":"astrid-runtime/astrid","slug":"legacy-and-operator-repl-histories-differ-refusin","errorCode":null,"errorMessage":"legacy and operator REPL histories differ; refusing to merge","messagePattern":"legacy and operator REPL histories differ; refusing to merge","errorType":"validation","errorClass":"std::io::Error::AlreadyExists","httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/repl.rs","lineNumber":245,"sourceCode":"    if !legacy_exists {\n        return Ok(());\n    }\n    let legacy = read_history_bounded(legacy_path)?;\n    match std::fs::symlink_metadata(new_path) {\n        Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_file() => {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"REPL history path is not a regular file: {}\",\n                    new_path.display()\n                ),\n            ));\n        },\n        Ok(_) => {\n            astrid_core::platform_fs::verify_no_redirects(new_path)?;\n            let current = read_history_bounded(new_path)?;\n            if current != legacy {\n                return Err(io::Error::new(\n                    io::ErrorKind::AlreadyExists,\n                    \"legacy and operator REPL histories differ; refusing to merge\",\n                ));\n            }\n        },\n        Err(error) if error.kind() == io::ErrorKind::NotFound => {\n            astrid_core::platform_fs::atomic_write_private_file(new_path, &legacy)?;\n        },\n        Err(error) => return Err(error),\n    }\n    std::fs::remove_file(legacy_path)?;\n    Ok(())\n}\n\nfn read_history_bounded(path: &Path) -> io::Result<Vec<u8>> {\n    astrid_core::platform_fs::verify_no_redirects(path)?;\n    let mut file = File::open(path)?;\n    let mut bytes = Vec::new();","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/repl.rs#L227-L263","documentation":"When both legacy and operator history files exist, the migration refuses to silently merge or overwrite: it reads both (bounded) and requires them to be byte-identical. If they differ, it fails with AlreadyExists and the message that it refuses to merge, forcing the operator to resolve the ambiguity manually instead of losing commands.","triggerScenarios":"migrate_legacy_history encounters both an existing legacy history and an existing new-path history whose contents differ (after verify_no_redirects passes).","commonSituations":"User ran both an old and a new CLI build concurrently, so each wrote its own history; a backup copy was restored to the legacy path after the operator history had advanced; version upgrade moved history but old file lingered with different bytes.","solutions":["Inspect both files and manually merge the commands you want to keep into the operator history file, then delete the legacy file","If the legacy file is obsolete, delete or archive it so migration proceeds","If the operator file is the stale one, replace it with the legacy contents before starting the REPL"],"exampleFix":"// before: both exist with different bytes -> refuses to merge\n// after\ncat ~/.astrid/legacy-history >> ~/.astrid/operator-history && rm ~/.astrid/legacy-history","handlingStrategy":"try-catch","validationCode":"// Detect the conflict before launching the REPL\nfn histories_conflict(legacy: &std::path::Path, new: &std::path::Path) -> std::io::Result<bool> {\n    let l = std::fs::read(legacy)?;\n    let n = std::fs::read(new)?;\n    Ok(legacy.exists() && new.exists() && l != n)\n}","typeGuard":null,"tryCatchPattern":"match Repl::new(...) {\n    Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists\n        && e.to_string().contains(\"refusing to merge\") => {\n        eprintln!(\"Merge {} into {} manually, then delete the legacy file.\", legacy_path.display(), new_path.display());\n    }\n    Err(e) => return Err(e.into()),\n    Ok(repl) => repl,\n}","preventionTips":["Never run two CLI versions concurrently against the same home directory","After upgrades, resolve leftover legacy files immediately rather than letting both accumulate","Back up both history files before manual merges"],"tags":["filesystem","migration","conflict","repl","history"],"backgroundTag":"conflicting-config-options","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"}