BloopAI/vibe-kanban · error

client server address already set

Error message

client server address already set

What it means

`set_server_addr` on the deployment's ClientInfo stores the server's listen address in a one-shot setter (internally an Option that can only be set once). The `.expect("client server address already set")` panics if the address was already assigned. `ServerHandle::serve()` is the only intended caller, so this fires only if `serve()` is somehow invoked twice on a client whose address was already set — e.g. two ServerHandles sharing one DeploymentImpl.

Source

Thrown at crates/server/src/startup.rs:47

    /// The base URL the main server is listening on.
    ///
    /// Uses `localhost` rather than `127.0.0.1` so that macOS ATS
    /// (App Transport Security) exception domains apply correctly in
    /// the Tauri desktop app — IP address literals aren't reliably
    /// matched by ATS, which causes WebSocket connections to fail.
    pub fn url(&self) -> String {
        format!("http://localhost:{}", self.port)
    }

    /// Run both the main and proxy servers until the shutdown token is cancelled.
    pub async fn serve(self) -> anyhow::Result<()> {
        // Start relay tunnel so the host registers with the relay server.
        // This must happen after the port is known (it's needed for local
        // proxying) and is shared between the standalone binary and Tauri.
        self.deployment
            .client_info()
            .set_server_addr(self.main_listener.local_addr()?)
            .expect("client server address already set");
        self.deployment
            .client_info()
            .set_preview_proxy_port(self.proxy_port)
            .expect("client preview proxy port already set");
        relay_registration::spawn_relay(&self.deployment).await;

        let app_router = routes::router(self.deployment.clone());
        let proxy_router: axum::Router = routes::preview::subdomain_router(self.deployment.clone())
            .layer(ValidateRequestHeaderLayer::custom(validate_origin));

        let main_shutdown = self.shutdown_token.clone();
        let proxy_shutdown = self.shutdown_token.clone();

        let main_server = axum::serve(self.main_listener, app_router)
            .with_graceful_shutdown(async move { main_shutdown.cancelled().await });
        let proxy_server = axum::serve(self.proxy_listener, proxy_router)
            .with_graceful_shutdown(async move { proxy_shutdown.cancelled().await });

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Ensure each server start creates a fresh deployment via `initialize_deployment`/`start_with_bind` rather than reusing the old ClientInfo
  2. Guard the startup code so `serve()` runs exactly once per process (e.g. OnceLock around the serve path)
  3. If a restart is intended, rebuild ClientInfo (or add a `reset`/`replace` API) before calling set_server_addr again
  4. Change the expect to a logged warning or idempotent overwrite (set_or_ignore) if re-setting the same address should be tolerated

Example fix

// before
let deployment = /* reused from previous run */;
ServerHandle::serve(...).await?; // panics: already set
// after
let deployment = initialize_deployment(shutdown_token).await?; // fresh ClientInfo
let handle = start_with_bind("localhost:0", "localhost:0", token).await?;
handle.serve().await?;
Defensive patterns

Strategy: validation

Validate before calling

// Before serve(), ensure ClientInfo state is fresh:
fn client_info_is_unset(info: &ClientInfo) -> bool {
    info.server_addr().is_none() && info.preview_proxy_port().is_none()
}

Try / catch

match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| handle.serve())) {
    Ok(fut) => fut.await,
    Err(_) => eprintln!("serve panicked: client_info already configured — reinitialize deployment"),
}

Prevention

When it happens

Trigger: Calling `serve()` (directly or via start_with_bind + serve) more than once on a DeploymentImpl whose client_info already had `set_server_addr` called — for example constructing two ServerHandle instances around the same deployment, or a restart path that reuses the old deployment object instead of re-initializing it.

Common situations: Tauri app or embedding code that restarts the server by calling `serve()` again on a cached ServerHandle/deployment; a test harness that calls serve() twice; refactoring that creates a second ServerHandle while reusing a single DeploymentImpl.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/bc8c4827b918a699. Report an issue: GitHub.