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

  1. Always obtain a fresh deployment (fresh ClientInfo) for each server start instead of reusing the previous one
  2. Ensure set_preview_proxy_port is called only from ServerHandle::serve and never elsewhere
  3. Make the setter idempotent or add a guard so re-setting the same value is a no-op instead of a panic
  4. 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

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


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