astrid-runtime/astrid · error
WinFsp daemon returned invalid readiness
Error message
WinFsp daemon returned invalid readiness: {ready:?} What it means
spawn_daemon waits for the daemon's stdout readiness line, which must be exactly "READY {mount_id}\n". Any other line (different text, different mount id, extra whitespace, or a wrapped error message) is rejected, ensuring the daemon is alive, is the right mount, and follows the handshake contract.
Solutions
- Run the daemon with the correct mount_id argument matching lease.mount_id.
- Strip banner/diagnostic output (send logging to stderr) so READY is the first stdout line.
- Check for CRLF: the daemon must emit "READY {id}\n", not "\r\n"; fix eprintln/print usage.
- Verify the daemon executable version matches the library's handshake protocol.
Example fix
// before (daemon)
println!("READY {}\r\n", mount_id); // CRLF mismatch
// after
println!("READY {}", mount_id); Defensive patterns
Strategy: try-catch
Validate before calling
let expected = format!("READY {}\n", lease.mount_id);
// after reading the line: assert_eq!(ready, expected); Type guard
fn is_valid_ready_line(line: &str, mount_id: &str) -> bool {
line == format!("READY {mount_id}\n")
} Try / catch
match spawn_daemon(&lease, &launch).await {
Err(e) if e.to_string().contains("invalid readiness") => {
// inspect daemon stderr, verify binary version and mount_id argument
Err(e)
},
other => other,
} Prevention
- Pass the daemon its mount_id exactly as held in the lease.
- Route all daemon logging to stderr so READY is the first stdout line.
- Emit "\n" (LF) line endings only, even on Windows.
- Pin the daemon binary version to match the library handshake.
When it happens
Trigger: spawn_daemon reads the first stdout line and it does not equal format!("READY {}\n", lease.mount_id): wrong mount_id passed to the daemon, daemon prints banner/diagnostics before READY, \r\n line endings on Windows, or a panic message on stdout.
Common situations: The daemon binary on PATH is a different/older version without the READY handshake; launch arguments transposed so the daemon receives another mount id; stdout redirected through a wrapper that adds prefixes; Windows CRLF line endings.
Related errors
- Daemon rejected connection
- detached FUSE service did not retain its control endpoint
- detached FUSE service exceeded the startup response size
- Handshake response too large
- opaque capsule assets cannot be symlinks
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/a3472299ab38dd5e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-storage-provider-winfsp/src/win.rs:578
stdin
.write_all(b"\n")
.await
.context("terminate daemon lease")?;
drop(stdin);
let stdout = child
.stdout
.take()
.context("WinFsp daemon stdout is unavailable")?;
let mut stdout = tokio::io::BufReader::new(stdout);
let mut ready = String::new();
stdout
.read_line(&mut ready)
.await
.context("read WinFsp daemon readiness")?;
let expected = format!("READY {}\n", lease.mount_id);
if ready != expected {
bail!("WinFsp daemon returned invalid readiness: {ready:?}");
}
if child.try_wait().context("inspect WinFsp daemon")?.is_some() {
bail!("WinFsp daemon exited immediately after readiness");
}
Result::<()>::Ok(())
};
match tokio::time::timeout(DAEMON_READY_TIMEOUT, success).await {
Ok(Ok(())) => Ok(()),
Ok(Err(error)) => {
let _ = child.kill().await;
let _ = child.wait().await;
Err(error.context("start WinFsp native filesystem"))
},
Err(_) => {
let _ = child.kill().await;
let _ = child.wait().await;
bail!("WinFsp daemon did not report readiness within 30 seconds");View on GitHub (pinned to affd8760f4)