astrid-runtime/astrid · error
legacy source changed while opening: {}
Error message
legacy source changed while opening: {} What it means
After opening the legacy source with O_NOFOLLOW, `read_bounded_file` compares the opened file's metadata (type and length) against the symlink metadata captured earlier and throws this error if they disagree. The library throws it to detect a race where the file changed (grew, shrank, or was replaced by a different non-file) between the initial stat and the open.
Source
Thrown at crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs:954
astrid_core::platform_fs::validate_private_file(path)?;
if metadata.len() > max {
return Err(io::Error::other(format!(
"legacy source exceeds migration cap: {}",
path.display()
)));
}
astrid_core::platform_fs::verify_no_redirects(path)?;
let mut options = OpenOptions::new();
options.read(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt as _;
options.custom_flags(nix::libc::O_NOFOLLOW | nix::libc::O_CLOEXEC);
}
let mut file = options.open(path)?;
let opened = file.metadata()?;
if !opened.is_file() || opened.len() != metadata.len() {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
format!("legacy source changed while opening: {}", path.display()),
));
}
let capacity = usize::try_from(opened.len())
.map_err(|_| io::Error::other("legacy source is too large for this platform"))?;
let mut bytes = Vec::with_capacity(capacity);
file.read_to_end(&mut bytes)?;
Ok(Some(bytes))
}
pub(super) fn sync_parent(path: &Path) -> io::Result<()> {
if let Some(parent) = path.parent() {
sync_directory(parent)?;
}
Ok(())
}
View on GitHub (pinned to affd8760f4)
Solutions
- Quiesce all processes that write to the legacy directory before migrating (stop services, pause sync tools).
- Re-run the migration once the legacy source is stable; the metadata comparison should then match.
- If it persists, verify no one is tampering: compare inode/size with `stat` across runs.
Example fix
// before: migration runs while a sync daemon rewrites the source $ astrid migrate # fails: legacy source changed while opening // after: pause the writer, then migrate $ systemctl stop dropbox-sync && astrid migrate
Defensive patterns
Strategy: retry
Try / catch
let mut attempts = 0;
loop {
match read_bounded_file(path, MAX) {
Err(e) if e.to_string().contains("changed while opening") && attempts < 3 => {
attempts += 1;
std::thread::sleep(Duration::from_millis(200));
}
r => break r,
}
} Prevention
- Stop all writers (services, sync clients, editors) touching the legacy directory before migrating.
- Run migration while the system is quiescent (single-user/rescue mode for system-level migration).
- If it recurs, check for concurrent processes with `fuser -v <path>` or lsof.
When it happens
Trigger: A concurrent writer or attacker modifies the legacy file's length or swaps the file between the initial `symlink_metadata` call and the guarded `options.open` inside read_bounded_file.
Common situations: Another process (sync client, editor, package manager) writes to the legacy directory while migration runs; deliberate tampering during a privileged migration; retrying migration while a background job rewrites the source.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- legacy state source changed type: {}
- principal-store directory changed while it was opened
- opened representation entry is redirected or not a regular f
- read materialized capsule metadata: {error:#}
- read materialized capsule authority: {error:#}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/39d606a41f913f42.
Report an issue: GitHub.