{"record":{"id":"bc8c4827b918a699","repo":"BloopAI/vibe-kanban","slug":"client-server-address-already-set-bc8c48","errorCode":null,"errorMessage":"client server address already set","messagePattern":"client server address already set","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/server/src/startup.rs","lineNumber":47,"sourceCode":"    /// The base URL the main server is listening on.\n    ///\n    /// Uses `localhost` rather than `127.0.0.1` so that macOS ATS\n    /// (App Transport Security) exception domains apply correctly in\n    /// the Tauri desktop app — IP address literals aren't reliably\n    /// matched by ATS, which causes WebSocket connections to fail.\n    pub fn url(&self) -> String {\n        format!(\"http://localhost:{}\", self.port)\n    }\n\n    /// Run both the main and proxy servers until the shutdown token is cancelled.\n    pub async fn serve(self) -> anyhow::Result<()> {\n        // Start relay tunnel so the host registers with the relay server.\n        // This must happen after the port is known (it's needed for local\n        // proxying) and is shared between the standalone binary and Tauri.\n        self.deployment\n            .client_info()\n            .set_server_addr(self.main_listener.local_addr()?)\n            .expect(\"client server address already set\");\n        self.deployment\n            .client_info()\n            .set_preview_proxy_port(self.proxy_port)\n            .expect(\"client preview proxy port already set\");\n        relay_registration::spawn_relay(&self.deployment).await;\n\n        let app_router = routes::router(self.deployment.clone());\n        let proxy_router: axum::Router = routes::preview::subdomain_router(self.deployment.clone())\n            .layer(ValidateRequestHeaderLayer::custom(validate_origin));\n\n        let main_shutdown = self.shutdown_token.clone();\n        let proxy_shutdown = self.shutdown_token.clone();\n\n        let main_server = axum::serve(self.main_listener, app_router)\n            .with_graceful_shutdown(async move { main_shutdown.cancelled().await });\n        let proxy_server = axum::serve(self.proxy_listener, proxy_router)\n            .with_graceful_shutdown(async move { proxy_shutdown.cancelled().await });\n","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/crates/server/src/startup.rs#L29-L65","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet deployment = /* reused from previous run */;\nServerHandle::serve(...).await?; // panics: already set\n// after\nlet deployment = initialize_deployment(shutdown_token).await?; // fresh ClientInfo\nlet handle = start_with_bind(\"localhost:0\", \"localhost:0\", token).await?;\nhandle.serve().await?;","handlingStrategy":"validation","validationCode":"// Before serve(), ensure ClientInfo state is fresh:\nfn client_info_is_unset(info: &ClientInfo) -> bool {\n    info.server_addr().is_none() && info.preview_proxy_port().is_none()\n}","typeGuard":null,"tryCatchPattern":"match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| handle.serve())) {\n    Ok(fut) => fut.await,\n    Err(_) => eprintln!(\"serve panicked: client_info already configured — reinitialize deployment\"),\n}","preventionTips":["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"],"tags":["startup","panic","one-shot-setter","server-address"],"backgroundTag":"one-shot-value-already-set","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}