astrid-runtime/astrid · error · io::Error

capability-relative exclusive rename is unsupported

Error message

capability-relative exclusive rename is unsupported

What it means

rename_no_replace performs an exclusive (no-replace) rename relative to two capability directories. On platforms/APIs lacking a no-replace rename (e.g. no renameat2 with RENAME_NOREPLACE, no MoveFileEx fallback), this stub returns ErrorKind::Unsupported. The operation is deliberately not emulated with a non-atomic check-then-rename, because that would lose the exclusivity guarantee.

Solutions

  1. Run on a platform whose libc provides atomic no-replace rename (Linux with renameat2, Windows MoveFileEx, macOS renamex_np)
  2. Upgrade the kernel/OS so the atomic-rename syscall is available
  3. File or contribute a platform implementation for rename_no_replace in principal_state/native_io.rs
  4. Restructure the caller to use a lock file or create-new (O_EXCL) protocol instead of exclusive rename if the platform truly cannot support it

Example fix

// before
rename_no_replace(&src_dir, &src, &dst_dir, &dst)?; // Unsupported on this platform
// after
let opts = OpenOptions::new().write(true).create_new(true);
let mut lock = opts.open(&dst)?; // O_EXCL claim, then write contents
lock.write_all(&payload)?;
Defensive patterns

Strategy: fallback

Validate before calling

if !atomic_no_replace_rename_supported() {
    // use lock-file or O_EXCL create protocol instead
    use_create_new_protocol();
}

Type guard

const EXCLUSIVE_RENAME_SUPPORTED: bool = cfg!(target_os = "linux") || cfg!(target_os = "windows");

Try / catch

match rename_result {
    Err(e) if e.kind() == io::ErrorKind::Unsupported => {
        // fall back to O_EXCL create-new claiming protocol
    }
    r => r,
}

Prevention

When it happens

Trigger: Calling rename_no_replace on a platform where the cfg-gated native implementation is absent — the fallback body unconditionally errors. Any principal-state flow performing exclusive renames on such a target hits this.

Common situations: Porting the storage layer to an OS without renameat2/RENAME_NOREPLACE semantics; older kernels lacking renameat2 on Linux; exotic filesystems where the syscall returns ENOSYS at build-time feature detection.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at crates/astrid-storage/src/principal_state/native_io.rs:631

#[cfg(windows)]
fn rename_no_replace(
    source_directory: &Dir,
    source: &Path,
    destination_directory: &Dir,
    destination: &Path,
) -> std::io::Result<()> {
    source_directory.rename(source, destination_directory, destination)
}

#[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
fn rename_no_replace(
    _source_directory: &Dir,
    _source: &Path,
    _destination_directory: &Dir,
    _destination: &Path,
) -> std::io::Result<()> {
    Err(std::io::Error::new(
        std::io::ErrorKind::Unsupported,
        "capability-relative exclusive rename is unsupported",
    ))
}

pub(super) fn private_file_identity(file: &File) -> StorageResult<PrivateFileIdentity> {
    let metadata = file
        .metadata()
        .map_err(|error| connection(format!("inspect private file handle: {error}")))?;
    if !metadata.is_file() {
        return Err(connection(
            "private file handle is not a regular file".to_owned(),
        ));
    }
    #[cfg(windows)]
    {
        use std::os::windows::fs::MetadataExt as _;
        use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;

View on GitHub (pinned to affd8760f4)