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

  1. Retry the connection; if transient it will succeed.
  2. Check that the bore server process is running and healthy on the target host.
  3. Inspect server logs for crashes and intermediary (proxy/firewall) logs for dropped connections.
  4. 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

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


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)