astrid-runtime/astrid · error
WinFsp daemon returned an invalid stop acknowledgement
Error message
WinFsp daemon returned an invalid stop acknowledgement
What it means
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.
Source
Thrown at crates/astrid-storage-provider-winfsp/src/win.rs:662
Err(error) => {
return Err(error).context(format!(
"connect WinFsp control endpoint {}",
control_path.display()
));
},
};
stream
.write_all(b"STOP")
.await
.context("send WinFsp stop")?;
stream.flush().await.context("flush WinFsp stop")?;
let mut acknowledgement = [0_u8; 1];
stream
.read_exact(&mut acknowledgement)
.await
.context("read WinFsp stop acknowledgement")?;
if acknowledgement[0] != b'S' {
bail!("WinFsp daemon returned an invalid stop acknowledgement");
}
Result::<()>::Ok(())
};
tokio::time::timeout(DAEMON_STOP_TIMEOUT, stop)
.await
.map_err(|_| anyhow::anyhow!("WinFsp stop timed out"))??;
let deadline = tokio::time::Instant::now()
.checked_add(DAEMON_STOP_TIMEOUT)
.ok_or_else(|| anyhow::anyhow!("WinFsp stop deadline overflow"))?;
while endpoint_is_present(control_path) {
if tokio::time::Instant::now() >= deadline {
bail!("WinFsp control endpoint remained live after stop");
}
tokio::time::sleep(Duration::from_millis(25)).await;
}
Ok(())
}View on GitHub (pinned to affd8760f4)
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.
Example fix
// before: mismatched daemon version silently breaks the ack protocol
let _ = std::process::Command::new("astrid-winfsp-daemon").spawn()?;
// after: pin/verify daemon version before starting or stopping it
let version = std::process::Command::new("astrid-winfsp-daemon").arg("--version").output()?;
assert_eq!(String::from_utf8_lossy(&version.stdout).trim(), expected_daemon_version); Defensive patterns
Strategy: try-catch
Validate before calling
// Best-effort pre-check: confirm the daemon process is the expected version before stopping
let version = std::process::Command::new(daemon_path).arg("--version").output()?;
if String::from_utf8_lossy(&version.stdout).trim() != expected_daemon_version {
// protocol mismatch likely; handle before calling stop_daemon
} Try / catch
match provider.stop_daemon().await {
Ok(()) => {},
Err(e) if e.to_string().contains("invalid stop acknowledgement") => {
// force-kill daemon and clean stale endpoint, then retry once
force_kill_daemon();
remove_stale_endpoint(&control_path);
provider.stop_daemon().await?;
}
Err(e) => return Err(e),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- WinFsp control endpoint remained live after stop
- WinFsp stop timed out
- shutdown stage daemon.shutdown_ack: unexpected response: {ot
- the WinFsp provider is available only on Windows
- no free Windows drive target is available; specify a directo
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/fd975ead7dfb0b15.
Report an issue: GitHub.