astrid-runtime/astrid · error

detached FUSE service returned an invalid process identity

Error message

detached FUSE service returned an invalid process identity

What it means

The parent validates the pid returned in ServiceStartup::Ready; pid == 0 is not a valid OS process identity (pid 0 is the idle/scheduler). If the service reports a zero pid, the handoff contract is broken and the parent cannot supervise or signal the daemon, so it rejects the startup.

Source

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

    .await
    .context("timed out waiting for the FUSE service")??;
    if read == 0 {
        bail!("detached FUSE service exited before readiness");
    }
    if line.len() > 64 * 1024 {
        bail!("detached FUSE service exceeded the startup response size");
    }
    match serde_json::from_str(&line)? {
        ServiceStartup::Ready {
            mount_id,
            pid,
            access,
        } => {
            if !control_path.exists() {
                bail!("detached FUSE service did not retain its control endpoint");
            }
            if pid == 0 {
                bail!("detached FUSE service returned an invalid process identity");
            }
            Ok(ControlReady {
                mount_id,
                pid,
                access,
            })
        },
        ServiceStartup::Error { message } => bail!("detached FUSE service failed: {message}"),
    }
}

#[derive(Debug)]
struct ControlReady {
    mount_id: StorageMountId,
    pid: u32,
    access: StorageProviderAccessV1,
}

View on GitHub (pinned to affd8760f4)

Solutions

  1. Fix the service to populate pid with std::process::id() (after any daemonize/fork step) before sending Ready
  2. Log the pid at the service just before sending Ready and compare with the parent's log
  3. If the service double-forks, report the pid of the process that will keep running, not the intermediate one
  4. Check the ServiceStartup serialization: ensure the pid field isn't skipped/defaulted to 0

Example fix

// before
let startup = ServiceStartup::Ready { mount_id, pid: 0, access };
// after
let startup = ServiceStartup::Ready { mount_id, pid: std::process::id(), access };
Defensive patterns

Strategy: validation

Validate before calling

// Caller-side sanity check on the returned ControlReady
if ready.pid == 0 {
    return Err(anyhow!("service reported invalid pid"));
}
if !pid_exists(ready.pid) {
    return Err(anyhow!("service pid {} is not a live process", ready.pid));
}

Type guard

fn valid_pid(ready: &ControlReady) -> bool { ready.pid != 0 }

Try / catch

let ready = read_service_startup(&mut child, &launch, &control_path).await?;
if !valid_pid(&ready) {
    let _ = child.kill().await;
    return Err(anyhow!("detached FUSE service returned pid 0; service build is broken"));
}

Prevention

When it happens

Trigger: The service serializes ServiceStartup::Ready with an uninitialized or placeholder pid — e.g. it reports the pid of a parent it intends to exit, fails std::process::id(), or the struct field defaulted to 0 during construction.

Common situations: Custom daemonization code (double-fork) losing track of the real child pid; a service build where pid was never assigned before replying; running under a sandbox/runtime where process introspection returns 0.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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