zed-industries/zed · error
Connection to TCP DAP timeout {address}
Error message
Connection to TCP DAP timeout {address} What it means
In the TCP DAP transport, a select! races a timer (Duration::from_millis(self.timeout)) against a retry loop of TcpStream::connect(address); when the timer wins, this bail names the host:port that never accepted a connection. The inner loop retries connects with 100ms gaps and only exits early if the spawned process dies (see the 'exited before debugger attached' error).
Source
Thrown at crates/dap/src/transport.rs:606
})
}
fn connect(
&mut self,
) -> Task<
Result<(
Box<dyn AsyncWrite + Unpin + Send + 'static>,
Box<dyn AsyncRead + Unpin + Send + 'static>,
)>,
> {
let executor = self.executor.clone();
let timeout = self.timeout;
let address = SocketAddr::new(self.host, self.port);
let process = self.process.clone();
executor.clone().spawn(async move {
select! {
_ = executor.timer(Duration::from_millis(timeout)).fuse() => {
anyhow::bail!("Connection to TCP DAP timeout {address}");
},
result = executor.clone().spawn(async move {
loop {
match TcpStream::connect(address).await {
Ok(stream) => {
let (read, write) = stream.split();
return Ok((Box::new(write) as _, Box::new(read) as _))
},
Err(_) => {
let has_process = process.lock().is_some();
if has_process {
let status = process.lock().as_mut().unwrap().try_status();
if let Ok(Some(_)) = status {
let child = process.lock().take().unwrap();
let output = child.output().await?;
let output = if output.stderr.is_empty() {
String::from_utf8_lossy(&output.stdout).to_string()
} else {View on GitHub (pinned to f4178619ac)
Solutions
- Confirm the server actually listens: `ss -ltnp | grep <port>` / `nc -vz <host> <port>`
- Align host/port in the debug config with the server's --listen/--port arguments
- Increase the timeout setting if the server legitimately needs longer to bind
- Check firewall/NAT rules drop-vs-reject behavior between Zed and the target
Defensive patterns
Strategy: retry
Validate before calling
// Verify the port is listening before starting the TCP transport
let stream = smol::net::TcpStream::connect((host, port)).await;
anyhow::ensure!(stream.is_ok(), "nothing listening on {host}:{port} yet"); Try / catch
match connect_tcp(host, port, timeout_ms).await {
Ok(io) => Ok(io),
Err(err) if err.to_string().contains("Connection to TCP DAP timeout") => {
// check whether the spawned process died, extend timeout, or surface a port-mismatch hint
Err(err.context("verify the debug server listens on the configured host:port"))
}
Err(err) => Err(err),
} Prevention
- Derive the connect port and the server's --listen flag from one config value so they cannot diverge
- Health-check the port (nc/ss) before invoking attach
- Size the timeout to the slowest realistic server start, and prefer refuse-fast failures over silent drops
When it happens
Trigger: Attaching over TCP with host/port config where nothing listens: the debug server (e.g. 'debugpy -w', 'dlv --headless --listen', java agent) starts slowly and misses the window, the port is wrong or occupied by another service that rejects DAP, a firewall silently drops SYN packets so connects never complete (no RST means the loop keeps retrying until timeout).
Common situations: Port mismatch between adapter config and server args; server listening on 127.0.0.1 while config uses another interface; containerized debug servers not publishing the port; firewall/NAT dropping instead of refusing; timeout value too small for a slow-starting server.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Timed out when connecting to debugger
- {output} error: process exited before debugger attached.
- Request failed: {}
- Received error response from adapter. Response: {:?}
- When using the `stdio` transport, the path to a debug adapte
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/a930cc00eebbacdd.
Report an issue: GitHub.