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
- Ensure each server start creates a fresh deployment via `initialize_deployment`/`start_with_bind` rather than reusing the old ClientInfo
- Guard the startup code so `serve()` runs exactly once per process (e.g. OnceLock around the serve path)
- If a restart is intended, rebuild ClientInfo (or add a `reset`/`replace` API) before calling set_server_addr again
- 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
- Initialize a new deployment for every server start
- Never call set_server_addr/set_preview_proxy_port outside serve()
- Serialize serve() calls with an AtomicBool/OnceLock
- Check ClientInfo accessors before re-setting values
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
- client preview proxy port already set
- client server address already set
- Default profiles v3 JSON is invalid
- request_id called for unsupported request variant
- handled non-session commands earlier
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/bc8c4827b918a699.
Report an issue: GitHub.