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

  1. Kill any stale daemon processes and delete leftover control-endpoint files, then retry stop_daemon.
  2. Ensure the WinFsp daemon binary version matches the astrid-storage-provider-winfsp crate version so the acknowledgement protocol agrees.
  3. Reproduce the daemon's stderr/stdout logging to see whether it errored during stop; fix that root cause first.
  4. 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

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


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/fd975ead7dfb0b15. Report an issue: GitHub.