astrid-runtime/astrid · error
detached FUSE service did not retain its control endpoint
Error message
detached FUSE service did not retain its control endpoint
What it means
After the detached FUSE service reports ServiceStartup::Ready, the parent verifies the Unix control socket at the agreed control_path still exists. If the path is gone, the service reported readiness but failed to keep its control endpoint alive, so the parent can never send control requests; it bails instead of returning a ControlReady whose socket would immediately fail.
Source
Thrown at crates/astrid-storage-provider-fuse/src/main.rs:713
let mut limited = reader.take(64 * 1024 + 1);
limited.read_line(&mut line).await
})
.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,View on GitHub (pinned to affd8760f4)
Solutions
- Check the service's socket lifecycle: bind the socket before replying Ready and never unlink it while running
- Ensure each mount has a unique mount_id / control path so concurrent services don't delete each other's sockets
- Verify control_path on disk matches the path the service actually bound (log it on both sides)
- Confirm the socket's parent directory is not a tmpdir subject to periodic cleaning
Example fix
// before: reply ready, then bind socket send_ready().await; bind_control_socket(&control_path)?; // after: bind first, then report ready bind_control_socket(&control_path)?; send_ready().await;
Defensive patterns
Strategy: validation
Validate before calling
// After receiving Ready, confirm the socket before using it
if !control_path.exists() {
return Err(anyhow!("control endpoint missing at {}", control_path.display()));
}
let meta = std::fs::metadata(&control_path)?;
if !meta.file_type().is_socket() { return Err(anyhow!("control path is not a socket")); } Type guard
fn control_endpoint_live(path: &Path) -> bool {
std::fs::metadata(path).map(|m| m.file_type().is_socket()).unwrap_or(false)
} Try / catch
match read_service_startup(&mut child, &launch, &control_path).await {
Ok(ready) => Ok(ready),
Err(e) if e.to_string().contains("did not retain its control endpoint") => {
// service exited or cleaned up its socket; inspect service logs and retry once
retry_mount_with_fresh_service().await
}
Err(e) => Err(e),
} Prevention
- Bind the control socket before sending Ready and never unlink it while the service runs
- Give every mount a unique mount_id so concurrent services can't delete each other's sockets
- Avoid placing sockets in directories with aggressive cleanup (tmpwatch/systemd-tmpfiles)
- Log the bound socket path on both parent and service and compare in tests
When it happens
Trigger: The service creates the control socket, replies Ready, then the socket file is deleted (cleanup code removing it early, a race with another process, or the socket living on a tmpfs that was cleaned). Also occurs if the service created the socket at a different path than the parent's control_path.
Common situations: Stale-socket cleanup logic in the service deleting the file after binding; two mounts sharing a mount id so one service's cleanup removes the other's socket; security software or tmpwatch removing unix sockets in temp dirs.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- detached FUSE service exceeded the startup response size
- opaque capsule assets cannot be symlinks: {}
- FUSE control request exceeds the bounded frame size
- FUSE service control response exceeds the bounded frame size
- FUSE service status failed [{code}]: {message}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/ccf6b359d1373e39.
Report an issue: GitHub.