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

inspect detached FUSE service after stderr sink handoff

Error message

inspect detached FUSE service after stderr sink handoff

What it means

After the stderr sink handoff, require_service_running_after_handoff polls the detached FUSE child with try_wait. If the child has already exited, it bails with the exit status, kills/waits as needed, and wraps any try_wait error with this context plus the final status.

Solutions

  1. Fix the detached service so it stays running after readiness — check the reported exit status for the root cause
  2. Validate FUSE mount options and mountpoint permissions before spawning the detached service
  3. Ensure nothing else reaps the child PID (double wait) so try_wait does not error
  4. Add a startup health/liveness check so a fast-exiting service is caught before handoff
Defensive patterns

Strategy: retry

Validate before calling

// verify the service stays up for a settle window after readiness
for _ in 0..10 {
    if Path::new(&pid_file).exists() { break; }
    tokio::time::sleep(Duration::from_millis(100)).await;
}
assert!(Path::new(&pid_file).exists(), "FUSE service exited right after readiness");

Try / catch

match result {
    Err(err) if err.to_string().contains("inspect detached FUSE service after stderr sink handoff") => {
        eprintln!("detached FUSE service exited/unstable after readiness: {err:#}");
        // retry spawn with backoff, then give up
    }
    other => other?,
}

Prevention

When it happens

Trigger: try_wait returns Err(error) while verifying the detached FUSE service is still alive after readiness — the child handle is invalid or the OS reports a wait error; Ok(Some(status)) takes the separate bail! path with the exit status.

Common situations: The FUSE service exits immediately after reporting readiness (bad mount options, permission denied on the mountpoint); the child process handle became invalid; PID reaping races in the supervisor.

Related errors


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

Appendix: source

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

            if stderr.is_empty() {
                Err(error.context(status_context))
            } else {
                Err(error.context(format!("{status_context}; stderr: {stderr}")))
            }
        },
    }
}

async fn require_service_running_after_handoff(
    child: &mut tokio::process::Child,
) -> Result<()> {
    match child.try_wait() {
        Ok(None) => Ok(()),
        Ok(Some(status)) => bail!("detached FUSE service exited after readiness: {status}"),
        Err(error) => {
            let _ = child.kill().await;
            let status = child.wait().await;
            Err(anyhow::Error::new(error)
                .context("inspect detached FUSE service after stderr sink handoff")
                .context(format!("detached FUSE service status: {status:?}")))
        },
    }
}

async fn read_service_startup(
    child: &mut tokio::process::Child,
    launch: &ServiceLaunch,
    control_path: &Path,
) -> Result<ControlReady> {
    let mut stdin = child
        .stdin
        .take()
        .context("FUSE service stdin is unavailable")?;
    let bytes = serde_json::to_vec(launch)?;
    stdin.write_all(&bytes).await?;
    stdin.write_all(b"\n").await?;

View on GitHub (pinned to affd8760f4)