astrid-runtime/astrid · error

FUSE service returned an incompatible unmount response

Error message

FUSE service returned an incompatible unmount response

What it means

The unmount flow expects ControlResponse::Done (or a Failure) from the FUSE control service. Receiving a ControlResponse::Status instead means the service answered an unmount request with status information, which the client treats as incompatible and bails (crates/astrid-storage-provider-fuse/src/main.rs:878).

Source

Thrown at crates/astrid-storage-provider-fuse/src/main.rs:878

        &record.mountpoint,
        record.auto_created_mountpoint,
    )?;
    Ok(())
}

fn control_unmount(control_path: &Path, acting_principal: &astrid_core::PrincipalId) -> Result<()> {
    match call_control(
        control_path,
        &ControlRequest::Unmount {
            requested_by: acting_principal.clone(),
        },
    )? {
        ControlResponse::Done => Ok(()),
        ControlResponse::Failure { code, message } => {
            bail!("FUSE service unmount failed [{code}]: {message}")
        },
        ControlResponse::Status { .. } => {
            bail!("FUSE service returned an incompatible unmount response")
        },
    }
}

fn cleanup_mountpoint(mountpoint: &Path, auto_created: bool) -> Result<()> {
    if auto_created
        && !mountpoint::mountinfo_contains(mountpoint)?
        && std::fs::symlink_metadata(mountpoint).is_ok_and(|metadata| metadata.is_dir())
        && std::fs::read_dir(mountpoint)?.next().is_none()
    {
        let _ = std::fs::remove_dir(mountpoint);
    }
    Ok(())
}

fn lease_from_response(body: AdminResponseBody) -> Result<StorageMountLeaseV1> {
    match body {
        AdminResponseBody::StorageMountLease(lease) => Ok(*lease),

View on GitHub (pinned to affd8760f4)

Solutions

  1. Verify the process bound to the control socket is the correct astrid FUSE service version and restart it if stale
  2. Remove stale socket files and retry the unmount
  3. Upgrade client and service together so ControlResponse variants line up

Example fix

// before
send_control(socket, ControlRequest::Unmount{..})?; // got Status back
// after
assert_control_service_version(socket)?; // confirm peer speaks same protocol
send_control(socket, ControlRequest::Unmount{..})?;
Defensive patterns

Strategy: type-guard

Validate before calling

let ok = matches!(resp, ControlResponse::Done | ControlResponse::Failure { .. });

Type guard

fn is_unmount_response(resp: &ControlResponse) -> bool { matches!(resp, ControlResponse::Done | ControlResponse::Failure { .. }) }

Try / catch

match unmount(...) { Err(e) if e.to_string().contains("incompatible unmount response") => { restart_service_and_retry()? }, r => r? }

Prevention

When it happens

Trigger: Sending ControlRequest::Unmount and getting a Status response — indicating the peer is not the expected control service or the request was misinterpreted (protocol/version mismatch).

Common situations: A stale or foreign process listening on the control socket; client/service version skew where Unmount handling changed; racing reconnect to a service that just restarted.

Related errors


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