Hmbown/CodeWhale · info

MCP SSE connect cancelled before authentication completed

Error message

MCP SSE connect cancelled before authentication completed

What it means

`run_sse_loop` waits for OAuth headers to resolve while listening on a cancellation token. If cancellation fires first (biased select gives it priority), the SSE connect is aborted with this error instead of proceeding or hanging.

Solutions

  1. If this is unexpected, check what cancelled the token before the connect completed and delay cancellation until after connect
  2. Treat this as a benign abort: catch it, exit the connect routine without retrying
  3. Only cancel the token after deciding to abandon the connection; for timeouts use the connect timeout instead

Example fix

// before
tokio::select! { _ = cancel.cancelled() => {}, res = connect(...) => res? }
// after
match connect(...).await {
    Err(e) if e.to_string().contains("cancelled before authentication") => { /* intentional abort */ }
    other => other?,
}
Defensive patterns

Strategy: try-catch

Validate before calling

if cancel_token.is_cancelled() {
    // don't even start the connect
    return Ok(());
}

Try / catch

match run_sse_loop(...).await {
    Err(e) if e.to_string().contains("cancelled") => info!("connect aborted by user"),
    Err(e) => return Err(e),
    Ok(v) => Ok(v),
}

Prevention

When it happens

Trigger: Calling the SSE connect with a `CancellationToken` that gets cancelled while `auth.resolved_headers()` is still pending — e.g. user aborts, app shutdown, or another task cancels during OAuth header resolution.

Common situations: Ctrl-C or disconnect while an MCP server is still completing OAuth; a supervisor cancelling a slow connect; retry logic cancelling a stale attempt.

Understand the failure class

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/bf9573d852b8a652. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/mcp/sse.rs:100

            sse_task,
        };
        transport
            .wait_for_endpoint(&wait_cancel_token, endpoint_timeout)
            .await?;
        Ok(transport)
    }

    async fn run_sse_loop(
        client: McpHttpClient,
        url: String,
        auth: McpHttpAuth,
        tx: tokio::sync::mpsc::Sender<SseInbound>,
        cancel_token: tokio_util::sync::CancellationToken,
    ) -> Result<()> {
        let headers = tokio::select! {
            biased;
            _ = cancel_token.cancelled() => {
                anyhow::bail!("MCP SSE connect cancelled before authentication completed")
            }
            headers = auth.resolved_headers() => headers?,
        };
        let request = apply_safe_custom_headers(
            with_default_mcp_http_headers(client.get(&url), false),
            &headers,
        );
        let response = tokio::select! {
            biased;
            _ = cancel_token.cancelled() => {
                anyhow::bail!("MCP SSE connect cancelled before the request completed")
            }
            response = client.send(request) => response.with_context(|| {
                format!(
                    "MCP SSE connect failed (transport=http url={})",
                    mask_url_secrets(&url),
                )
            })?,

View on GitHub (pinned to 73e0f67d83)