astrid-runtime/astrid · error

quarantine to

Error message

quarantine {} to {}: {error}

What it means

This error wraps a failed `fs::rename` while quarantining an unexpected file found in the principal home during migration. The kernel first moves the stray file into a private quarantine directory before continuing; if the OS rename fails, the original OS error kind is preserved and both source and destination paths are embedded in the message. It exists so migration never silently destroys or loses unexplained files.

Solutions

  1. Re-run the operation; transient locks from sync/backup tools usually clear once those tools are paused
  2. Exclude the principal home directory from file sync/backup software
  3. Check that the home and migrations directory are on the same filesystem and writable by the current user
  4. Inspect the wrapped OS error kind in the message (e.g. EXDEV, EACCES, ENOENT) to target the filesystem/permission cause

Example fix

// before
fs::rename(source, &destination)?;
// after
// fall back to copy+remove when rename fails across devices
match fs::rename(source, &destination) {
    Ok(()) => {},
    Err(e) if e.kind() == io::ErrorKind::CrossesDevices => {
        fs::copy(source, &destination)?;
        fs::remove_file(source)?;
    }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling admission, ensure the home is quiescent and writable
let meta = std::fs::metadata(source)?;
if !meta.is_file() { return Err("unexpected non-file entry"); }
let test = std::fs::OpenOptions::new().append(true).open(source.parent().unwrap())?;

Try / catch

match err.downcast_ref::<io::Error>() {
    Some(e) if e.kind() == io::ErrorKind::CrossesDevices => /* copy+remove fallback */,
    Some(e) if e.kind() == io::ErrorKind::PermissionDenied => /* fix perms, retry */,
    _ => /* surface to user */,
}

Prevention

When it happens

Trigger: `quarantine_entry` (via `admit_or_quarantine_entry`) calls `fs::rename(source, destination)` and the OS rejects it: source file deleted/renamed concurrently, destination collision despite `unique_quarantine_path`, cross-device rename, or permission issues on the source or migrations dir.

Common situations: Another process (editor, sync tool like Dropbox/Nextcloud, backup agent) is touching files inside the home while migration runs; the home spans multiple filesystems so rename is EXDEV; the migrations directory was manually created with wrong permissions.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/8a4a46d452784cb5. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-kernel/src/principal_home_migration/unbound.rs:241

            io::ErrorKind::InvalidData,
            format!("principal {alias} has an invalid genesis public key: {error}"),
        )
    })?;
    Ok(public_key.into())
}

fn quarantine_entry(
    home: &AstridHome,
    source: &Path,
    file_name: &OsStr,
    reason: &str,
) -> io::Result<()> {
    let quarantine_root = home.migrations_dir().join(QUARANTINE_DIR);
    astrid_core::platform_fs::ensure_private_directory(&quarantine_root)?;
    let destination = unique_quarantine_path(&quarantine_root, file_name)?;
    let source_parent = source.parent().map(Path::to_path_buf);
    fs::rename(source, &destination).map_err(|error| {
        io::Error::new(
            error.kind(),
            format!(
                "quarantine {} to {}: {error}",
                source.display(),
                destination.display()
            ),
        )
    })?;
    if let Some(parent) = source_parent.as_deref() {
        sync_directory(parent)?;
    }
    sync_parent(&destination)?;
    let sidecar = destination.with_file_name(format!(
        "{}.original-name",
        destination
            .file_name()
            .map_or("leftover", |name| name.to_str().unwrap_or("leftover"))
    ));

View on GitHub (pinned to affd8760f4)