{"record":{"id":"ccf6b359d1373e39","repo":"astrid-runtime/astrid","slug":"detached-fuse-service-did-not-retain-its-control-e","errorCode":null,"errorMessage":"detached FUSE service did not retain its control endpoint","messagePattern":"detached FUSE service did not retain its control endpoint","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-storage-provider-fuse/src/main.rs","lineNumber":713,"sourceCode":"        let mut limited = reader.take(64 * 1024 + 1);\n        limited.read_line(&mut line).await\n    })\n    .await\n    .context(\"timed out waiting for the FUSE service\")??;\n    if read == 0 {\n        bail!(\"detached FUSE service exited before readiness\");\n    }\n    if line.len() > 64 * 1024 {\n        bail!(\"detached FUSE service exceeded the startup response size\");\n    }\n    match serde_json::from_str(&line)? {\n        ServiceStartup::Ready {\n            mount_id,\n            pid,\n            access,\n        } => {\n            if !control_path.exists() {\n                bail!(\"detached FUSE service did not retain its control endpoint\");\n            }\n            if pid == 0 {\n                bail!(\"detached FUSE service returned an invalid process identity\");\n            }\n            Ok(ControlReady {\n                mount_id,\n                pid,\n                access,\n            })\n        },\n        ServiceStartup::Error { message } => bail!(\"detached FUSE service failed: {message}\"),\n    }\n}\n\n#[derive(Debug)]\nstruct ControlReady {\n    mount_id: StorageMountId,\n    pid: u32,","sourceCodeStart":695,"sourceCodeEnd":731,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-storage-provider-fuse/src/main.rs#L695-L731","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: reply ready, then bind socket\nsend_ready().await;\nbind_control_socket(&control_path)?;\n// after: bind first, then report ready\nbind_control_socket(&control_path)?;\nsend_ready().await;","handlingStrategy":"validation","validationCode":"// After receiving Ready, confirm the socket before using it\nif !control_path.exists() {\n    return Err(anyhow!(\"control endpoint missing at {}\", control_path.display()));\n}\nlet meta = std::fs::metadata(&control_path)?;\nif !meta.file_type().is_socket() { return Err(anyhow!(\"control path is not a socket\")); }","typeGuard":"fn control_endpoint_live(path: &Path) -> bool {\n    std::fs::metadata(path).map(|m| m.file_type().is_socket()).unwrap_or(false)\n}","tryCatchPattern":"match read_service_startup(&mut child, &launch, &control_path).await {\n    Ok(ready) => Ok(ready),\n    Err(e) if e.to_string().contains(\"did not retain its control endpoint\") => {\n        // service exited or cleaned up its socket; inspect service logs and retry once\n        retry_mount_with_fresh_service().await\n    }\n    Err(e) => Err(e),\n}","preventionTips":["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"],"tags":["fuse","ipc","unix-socket","process"],"backgroundTag":"resource-not-found","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}