astrid-runtime/astrid · warning
named-pipe client disconnected before transport authenticati
Error message
named-pipe client disconnected before transport authentication
What it means
On Windows, if a named-pipe client closes (or the pipe breaks) before completing transport-level authentication, Astrid surfaces an io::ErrorKind::UnexpectedEof error via pre_authentication_eof(), optionally chaining the underlying OS error in the message. This distinguishes an early, unauthenticated disconnect from post-auth protocol errors; is_pre_authentication_disconnect classifies ERROR_BROKEN_PIPE / ERROR_NO_DATA / ERROR_PIPE_NOT_CONNECTED as this case.
Source
Thrown at crates/astrid-core/src/local_transport/windows.rs:312
};
require_current_user_effective_client(&stream)?;
// Effective-token impersonation above is the authorization boundary.
// The process-token check is independent defense in depth and pins the
// client process object while re-reading the pipe-reported PID.
let peer = require_current_user_process_peer(&stream)?;
validate_pipe_security(stream_handle(&stream)?)?;
peer.ensure_still_peer(&stream)?;
Ok(stream)
}
fn pre_authentication_eof(source: Option<&io::Error>) -> io::Error {
let message = match source {
Some(source) => {
format!("named-pipe client disconnected before transport authentication: {source}")
},
None => "named-pipe client disconnected before transport authentication".to_string(),
};
io::Error::new(io::ErrorKind::UnexpectedEof, message)
}
fn is_pre_authentication_disconnect(error: &io::Error) -> bool {
matches!(
error.raw_os_error().map(i32::cast_unsigned),
Some(ERROR_BROKEN_PIPE | ERROR_NO_DATA | ERROR_PIPE_NOT_CONNECTED)
)
}
pub(super) fn split(
stream: LocalStream,
) -> (
tokio::io::ReadHalf<LocalStream>,
tokio::io::WriteHalf<LocalStream>,
) {
tokio::io::split(stream)
}
View on GitHub (pinned to affd8760f4)
Solutions
- Treat this as a benign remote-side disconnect: server-side, catch io::ErrorKind::UnexpectedEof and continue the accept loop instead of failing
- Investigate the client: ensure it runs the full authentication handshake before dropping the connection
- If it happens at scale, check for scanners/load balancers probing the pipe and exclude the endpoint from probing
Example fix
// server accept loop
match listener.accept().await {
Ok((stream, _)) => spawn_auth(stream),
Err(e) if is_pre_authentication_disconnect(&e) => continue, // client hung up early
Err(e) => return Err(e),
} Defensive patterns
Strategy: try-catch
Try / catch
match listener.accept().await {
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => continue, // client hung up pre-auth
other => other?,
} Prevention
- Treat early client disconnects as expected noise: log at debug, keep the accept loop alive
- Ensure clients perform the full authentication handshake before dropping
- Exclude the pipe endpoint from health-checkers/scanners that connect-and-close
When it happens
Trigger: accept() reads from a freshly connected pipe client and the client process exits or its connection breaks (ERROR_BROKEN_PIPE, ERROR_NO_DATA, ERROR_PIPE_NOT_CONNECTED) before the handshake/auth exchange finishes.
Common situations: A health-check or port scanner connects and immediately closes; the client crashed or was killed mid-handshake; a client with the wrong protocol gives up before authenticating; network/pipe interruption during startup.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Windows named-pipe endpoint disappeared while waiting
- Windows named-pipe endpoint denied access while waiting
- Windows named-pipe endpoint is absent
- Windows named-pipe endpoint is occupied but unavailable
- Windows named-pipe endpoints are kernel-owned and cannot be
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/5eed94fe0c3d003f.
Report an issue: GitHub.