astrid-runtime/astrid · error · std::io::Error::AlreadyExists
legacy and operator REPL histories differ; refusing to merge
Error message
legacy and operator REPL histories differ; refusing to merge
What it means
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.
Source
Thrown at crates/astrid-cli/src/repl.rs:245
if !legacy_exists {
return Ok(());
}
let legacy = read_history_bounded(legacy_path)?;
match std::fs::symlink_metadata(new_path) {
Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_file() => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"REPL history path is not a regular file: {}",
new_path.display()
),
));
},
Ok(_) => {
astrid_core::platform_fs::verify_no_redirects(new_path)?;
let current = read_history_bounded(new_path)?;
if current != legacy {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
"legacy and operator REPL histories differ; refusing to merge",
));
}
},
Err(error) if error.kind() == io::ErrorKind::NotFound => {
astrid_core::platform_fs::atomic_write_private_file(new_path, &legacy)?;
},
Err(error) => return Err(error),
}
std::fs::remove_file(legacy_path)?;
Ok(())
}
fn read_history_bounded(path: &Path) -> io::Result<Vec<u8>> {
astrid_core::platform_fs::verify_no_redirects(path)?;
let mut file = File::open(path)?;
let mut bytes = Vec::new();View on GitHub (pinned to affd8760f4)
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
Example fix
// before: both exist with different bytes -> refuses to merge // after cat ~/.astrid/legacy-history >> ~/.astrid/operator-history && rm ~/.astrid/legacy-history
Defensive patterns
Strategy: try-catch
Validate before calling
// Detect the conflict before launching the REPL
fn histories_conflict(legacy: &std::path::Path, new: &std::path::Path) -> std::io::Result<bool> {
let l = std::fs::read(legacy)?;
let n = std::fs::read(new)?;
Ok(legacy.exists() && new.exists() && l != n)
} Try / catch
match Repl::new(...) {
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists
&& e.to_string().contains("refusing to merge") => {
eprintln!("Merge {} into {} manually, then delete the legacy file.", legacy_path.display(), new_path.display());
}
Err(e) => return Err(e.into()),
Ok(repl) => repl,
} Prevention
- 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
When it happens
Trigger: migrate_legacy_history encounters both an existing legacy history and an existing new-path history whose contents differ (after verify_no_redirects passes).
Common situations: 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.
Related errors
- legacy REPL history is not a regular file: {}
- legacy audit retirement is ambiguous: {}
- legacy distro conflict at {path}: {detail}
- legacy log conflict at {}: {detail}
- legacy capsule directory is not a regular directory: {}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/aee3e2a04ecc37a6.
Report an issue: GitHub.