BloopAI/vibe-kanban · error
client preview proxy port already set
Error message
client preview proxy port already set
What it means
Same one-shot setter pattern as the server address: `set_preview_proxy_port` can only be called once on the deployment's ClientInfo, and the `.expect("client preview proxy port already set")` panics when the port was previously stored. It sits immediately after `set_server_addr` in `ServerHandle::serve()`, so any path that calls serve() twice (or sets the port elsewhere first) triggers it.
Source
Thrown at crates/server/src/startup.rs:51
/// 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 });
let main_handle = tokio::spawn(async move {
if let Err(e) = main_server.await {
tracing::error!("Main server error: {}", e);
}View on GitHub (pinned to 4deb7eca8f)
Solutions
- Always obtain a fresh deployment (fresh ClientInfo) for each server start instead of reusing the previous one
- Ensure set_preview_proxy_port is called only from ServerHandle::serve and never elsewhere
- Make the setter idempotent or add a guard so re-setting the same value is a no-op instead of a panic
- Serialize startup so only one serve() call can run per process (OnceLock or a dedicated supervisor task)
Example fix
// before
self.deployment.client_info().set_preview_proxy_port(self.proxy_port)
.expect("client preview proxy port already set");
// after
if let Err(e) = self.deployment.client_info().set_preview_proxy_port(self.proxy_port) {
tracing::warn!("preview proxy port already set ({}), continuing", e);
} Defensive patterns
Strategy: validation
Validate before calling
// Guard against double serve()/port set:
static SERVED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
fn should_serve() -> bool {
!SERVED.swap(true, std::sync::atomic::Ordering::SeqCst)
} Try / catch
// Catch the panic from serve() when embedding:
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
// spawn serve() and drive it
}));
if result.is_err() { eprintln!("startup panicked (port already set?) — rebuild deployment"); } Prevention
- Make set_preview_proxy_port the sole responsibility of ServerHandle::serve
- Restart servers only with a newly initialized deployment
- Avoid constructing multiple ServerHandles around one DeploymentImpl
- Consider making the setter idempotent for same-value re-sets
When it happens
Trigger: A second invocation of `ServerHandle::serve()` on a deployment whose preview proxy port was already set — e.g. two ServerHandles wrapping one DeploymentImpl, or external code calling `client_info().set_preview_proxy_port()` before serve().
Common situations: Restart logic in the Tauri/desktop wrapper that reuses a deployment across server restarts; test code setting the proxy port manually then calling serve(); concurrent serve() invocations racing on the same ClientInfo.
Related errors
- client server address 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/529862ead7a8b49b.
Report an issue: GitHub.