pbakaus/impeccable · error

Error: listen EADDRINUSE: {}

Error message

Error: listen EADDRINUSE: {}

What it means

The tiny_http server failed to bind 127.0.0.1 on the requested port. The command reports it as a Node-style `listen EADDRINUSE` error and exits 1. EADDRINUSE means the address/port is already in use by another socket.

Source

Thrown at crates/context/src/serve_question.rs:417

                return 1;
            }
        },
        None => io.stdin().to_string(),
    };
    let detached_key = if a.has("detached-serve") { a.arg("key") } else { None };
    let state = Arc::new(Mutex::new(ServerState::new(cwd.clone(), qdir.clone(), detached_key.clone(), idle_grace_ms)));
    {
        let mut st = state.lock().unwrap();
        if let Err(msg) = st.load_round(&raw) {
            io.err(&format!("serve-question: {}\n", msg));
            return 1;
        }
    }
    let port = if port_arg.is_finite() && port_arg >= 0.0 { port_arg as u16 } else { 0 };
    let server = match tiny_http::Server::http(("127.0.0.1", port)) {
        Ok(s) => s,
        Err(e) => {
            io.err(&format!("Error: listen EADDRINUSE: {}\n", e));
            return 1;
        }
    };
    let actual_port = server.server_addr().to_ip().map(|a| a.port()).unwrap_or(port);
    let url = format!("http://127.0.0.1:{}/", actual_port);
    if a.has("detached-serve") {
        let _ = std::fs::create_dir_all(&qdir);
        let key = a.arg("key").unwrap_or_default();
        let st = json!({ "pid": std::process::id(), "port": actual_port, "url": url });
        let _ = std::fs::write(state_file(&qdir, &key), json_compact(&st));
    } else {
        io.out(&format!("QUESTION URL: {}\n", url));
        io.out("Waiting for the user to choose in the browser (Ctrl-C aborts)...\n");
    }
    if !a.has("no-open") {
        open_system_browser(&url, &env);
    }
    let _ = io.stdout.flush();

View on GitHub (pinned to 2bc2879276)

Solutions

  1. Kill the existing process holding the port (`lsof -iTCP:<port> -sTCP:LISTEN`).
  2. Omit the --port flag or pass 0 to let the OS pick a free ephemeral port.
  3. Choose a different explicit port.
  4. If a detached serve is still alive, reuse its URL rather than serving again.

Example fix

// before
impeccable serve-question --port 3000 detached-serve payload.json
// after
impeccable serve-question payload.json   // port 0, OS-assigned
Defensive patterns

Strategy: fallback

Validate before calling

const net = require('net');
function portFree(port) {
  return new Promise(res => {
    const s = net.createServer();
    s.once('error', () => res(false));
    s.listen(port, '127.0.0.1', () => s.close(() => res(true)));
  });
}

Try / catch

try {
  run(`impeccable serve-question --port ${port} ...`);
} catch (e) {
  if (e.stderr.includes('EADDRINUSE')) return run(`impeccable serve-question ...`); // omit --port
  throw e;
}

Prevention

When it happens

Trigger: `serve-question --port <n>` (or a detached-serve key invocation with an explicit port) where an explicit finite port >= 0 is requested and another process already listens on it. Port 0 (ephemeral) cannot produce this.

Common situations: A previous serve-question instance still running (especially detached-serve), another dev server on the same port, or a lingering TIME_WAIT/locked port in containers.

Related errors


AI-assisted analysis of pbakaus/impeccable@2bc2879276 (2026-09-08). Data as JSON: /api/errors/87027a93fc44487e. Report an issue: GitHub.