ekzhang/bore · error
unexpected EOF
Error message
unexpected EOF
What it means
In `Client::new`, the stream returned `None` (EOF) while waiting for the server's `Hello` response, so no response ever arrived. The connection was closed by the remote side or an intermediary before the handshake completed.
Solutions
- Retry the connection; if transient it will succeed.
- Check that the bore server process is running and healthy on the target host.
- Inspect server logs for crashes and intermediary (proxy/firewall) logs for dropped connections.
- Increase proxy/firewall connect timeouts if they are cutting the connection during handshake.
Example fix
// before (retry without checking)
bore local 3000 --to bore.example.com
// after (in code: retry with backoff on Err)
for attempt in 0..3 {
match Client::new(...).await {
Ok(c) => break,
Err(e) if e.to_string().contains("unexpected EOF") => tokio::time::sleep(delay * attempt).await,
Err(e) => return Err(e),
}
} Defensive patterns
Strategy: retry
Validate before calling
// Cheap liveness probe before the handshake
if std::net::TcpStream::connect_timeout(&addr, std::time::Duration::from_secs(5)).is_err() {
bail!("server unreachable at {addr}");
} Try / catch
match Client::new(...).await {
Err(e) if e.to_string().contains("unexpected EOF") => {
tokio::time::sleep(backoff).await;
retry_connect() // bounded retries with exponential backoff
}
other => other,
} Prevention
- Implement bounded retries with exponential backoff around Client::new.
- Monitor bore server health (systemd/k8s probes) so dead instances are replaced.
- Set generous proxy/firewall timeouts so the handshake isn't cut.
When it happens
Trigger: Server process crashed or restarted mid-handshake; a firewall/NAT/load balancer dropped the connection; server actively closed the TCP connection on accept; timeout path where the underlying stream reached EOF before any frame arrived.
Common situations: Server behind a proxy with a very short idle/connect timeout; bore server not actually running but something accepted the TCP connection then closed it; transient network interruption; Docker/Kubernetes service with no healthy backend.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
Related errors
- server requires secret, but no secret was provided
- expected authentication challenge, but no secret was…
- server error
- server requires authentication, but no client secret was…
- unexpected initial non-hello message
AI-assisted analysis of ekzhang/bore@00a735a899 (2026-09-08).
Data as JSON: /api/errors/8c5dd8a7b9915bc9.
Report an issue: GitHub.
Appendix: source
Thrown at src/client.rs:57
to: &str,
port: u16,
secret: Option<&str>,
) -> Result<Self> {
let mut stream = Delimited::new(connect_with_timeout(to, CONTROL_PORT).await?);
let auth = secret.map(Authenticator::new);
if let Some(auth) = &auth {
auth.client_handshake(&mut stream).await?;
}
stream.send(ClientMessage::Hello(port)).await?;
let remote_port = match stream.recv_timeout().await? {
Some(ServerMessage::Hello(remote_port)) => remote_port,
Some(ServerMessage::Error(message)) => bail!("server error: {message}"),
Some(ServerMessage::Challenge(_)) => {
bail!("server requires authentication, but no client secret was provided");
}
Some(_) => bail!("unexpected initial non-hello message"),
None => bail!("unexpected EOF"),
};
info!(remote_port, "connected to server");
info!("listening at {to}:{remote_port}");
Ok(Client {
conn: Some(stream),
to: to.to_string(),
local_host: local_host.to_string(),
local_port,
remote_port,
auth,
})
}
/// Returns the port publicly available on the remote.
pub fn remote_port(&self) -> u16 {
self.remote_port
}View on GitHub (pinned to 00a735a899)