{"record":{"id":"fd975ead7dfb0b15","repo":"astrid-runtime/astrid","slug":"winfsp-daemon-returned-an-invalid-stop-acknowledge","errorCode":null,"errorMessage":"WinFsp daemon returned an invalid stop acknowledgement","messagePattern":"WinFsp daemon returned an invalid stop acknowledgement","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-storage-provider-winfsp/src/win.rs","lineNumber":662,"sourceCode":"            Err(error) => {\n                return Err(error).context(format!(\n                    \"connect WinFsp control endpoint {}\",\n                    control_path.display()\n                ));\n            },\n        };\n        stream\n            .write_all(b\"STOP\")\n            .await\n            .context(\"send WinFsp stop\")?;\n        stream.flush().await.context(\"flush WinFsp stop\")?;\n        let mut acknowledgement = [0_u8; 1];\n        stream\n            .read_exact(&mut acknowledgement)\n            .await\n            .context(\"read WinFsp stop acknowledgement\")?;\n        if acknowledgement[0] != b'S' {\n            bail!(\"WinFsp daemon returned an invalid stop acknowledgement\");\n        }\n        Result::<()>::Ok(())\n    };\n    tokio::time::timeout(DAEMON_STOP_TIMEOUT, stop)\n        .await\n        .map_err(|_| anyhow::anyhow!(\"WinFsp stop timed out\"))??;\n\n    let deadline = tokio::time::Instant::now()\n        .checked_add(DAEMON_STOP_TIMEOUT)\n        .ok_or_else(|| anyhow::anyhow!(\"WinFsp stop deadline overflow\"))?;\n    while endpoint_is_present(control_path) {\n        if tokio::time::Instant::now() >= deadline {\n            bail!(\"WinFsp control endpoint remained live after stop\");\n        }\n        tokio::time::sleep(Duration::from_millis(25)).await;\n    }\n    Ok(())\n}","sourceCodeStart":644,"sourceCodeEnd":680,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-storage-provider-winfsp/src/win.rs#L644-L680","documentation":"stop_daemon asks the WinFsp daemon to shut down over its control pipe and then reads a single-byte acknowledgement, which must be the literal byte b'S'. Any other byte means the daemon did not confirm a clean stop, so the library bails instead of silently leaving the filesystem mounted or half-torn-down. It guards callers against protocol drift or a daemon that stopped working but replied with garbage.","triggerScenarios":"Calling stop_daemon where the daemon process responds on the control stream with a byte other than b'S' — e.g. an error string, a NUL byte from a crashed/early-exited daemon, or a protocol version mismatch between the caller and the daemon binary that acknowledges differently.","commonSituations":"Daemon binary and host crate versions are out of sync (older daemon acknowledges with a different protocol byte); the daemon panicked mid-stop and wrote an error message; the control pipe was connected to something else (stale endpoint from a previous crashed run) that echoes unrelated data.","solutions":["Kill any stale daemon processes and delete leftover control-endpoint files, then retry stop_daemon.","Ensure the WinFsp daemon binary version matches the astrid-storage-provider-winfsp crate version so the acknowledgement protocol agrees.","Reproduce the daemon's stderr/stdout logging to see whether it errored during stop; fix that root cause first.","If unmanageable, treat stop as failed and forcibly terminate the daemon + unmount, then re-run."],"exampleFix":"// before: mismatched daemon version silently breaks the ack protocol\nlet _ = std::process::Command::new(\"astrid-winfsp-daemon\").spawn()?;\n// after: pin/verify daemon version before starting or stopping it\nlet version = std::process::Command::new(\"astrid-winfsp-daemon\").arg(\"--version\").output()?;\nassert_eq!(String::from_utf8_lossy(&version.stdout).trim(), expected_daemon_version);","handlingStrategy":"try-catch","validationCode":"// Best-effort pre-check: confirm the daemon process is the expected version before stopping\nlet version = std::process::Command::new(daemon_path).arg(\"--version\").output()?;\nif String::from_utf8_lossy(&version.stdout).trim() != expected_daemon_version {\n    // protocol mismatch likely; handle before calling stop_daemon\n}","typeGuard":null,"tryCatchPattern":"match provider.stop_daemon().await {\n    Ok(()) => {},\n    Err(e) if e.to_string().contains(\"invalid stop acknowledgement\") => {\n        // force-kill daemon and clean stale endpoint, then retry once\n        force_kill_daemon();\n        remove_stale_endpoint(&control_path);\n        provider.stop_daemon().await?;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Pin the daemon binary version to the host crate version and verify at startup.","Clean up stale control endpoints and orphaned daemons before starting a new session.","Capture daemon stderr during stop to diagnose non-'S' acknowledgements.","Add an integration test covering stop while the daemon is mid-operation."],"tags":["winfsp","protocol","shutdown","windows"],"backgroundTag":"unexpected-response-shape","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"}