Kuberwastaken/claurst · error

Callback server dropped

Error message

Callback server dropped

What it means

In wait_for_auth_code_impl, the mpsc receiver for the loopback callback server yielded None, meaning the callback server task terminated without sending an authorization code. The unwrap_or_else converts that channel closure into this error. Normal result delivery would have carried either a code or the server's own error.

Solutions

  1. Re-run the OAuth login flow; this is usually a one-off task failure.
  2. Check that the loopback redirect port is free and not blocked by a firewall.
  3. Use the manual paste path (paste the auth code into stdin) if the callback server cannot bind.
  4. Check application logs for a panic in the callback server task.
Defensive patterns

Strategy: fallback

Validate before calling

// ensure the callback port is bindable before starting the server
TcpListener::bind(("127.0.0.1", port)).await
    .map_err(|e| anyhow!("callback port {} unavailable: {}", port, e))?;

Try / catch

// fall back to manual paste when the callback channel dies
tokio::select! {
    result = cb_rx => result.unwrap_or_else(|_| Err(anyhow!("Callback server dropped"))),
    _ = prompt_manual_paste() => prompt_manual_paste().await,
}

Prevention

When it happens

Trigger: tokio::select! branch `result = cb_rx` receives Err (channel closed) instead of a message: the local HTTP callback server task in run_oauth_login_flow_with_label panicked, was dropped, or shut down before the browser hit the redirect.

Common situations: Port conflict killing the loopback server at startup; another agent/process killing the task; a panic inside the callback handler; OS firewall or sandbox terminating the listener.

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/883a7b68bed63c3f. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/cli/src/oauth_flow.rs:531

        let _ = cb_tx.send(result);
    });

    let (paste_tx, paste_rx) = tokio::sync::oneshot::channel::<String>();
    tokio::spawn(async move {
        if let Ok(line) = read_line_from_stdin().await {
            let trimmed = line.trim().to_string();
            if !trimmed.is_empty() {
                let _ = paste_tx.send(trimmed);
            }
        }
    });

    tokio::select! {
        result = cb_rx => {
            // Loopback callback: code came clean from the query string and was
            // authorized against the localhost redirect_uri.
            result
                .unwrap_or_else(|_| Err(anyhow::anyhow!("Callback server dropped")))
                .map(|code| (code, false))
        }
        code = paste_rx => {
            // Manual paste: the page hands back "<code>#<state>"; keep only the
            // code part. This path authorized against MANUAL_REDIRECT_URL.
            let raw = code.map_err(|_| anyhow::anyhow!("Stdin closed unexpectedly"))?;
            let code_only = raw.split('#').next().unwrap_or(&raw).trim().to_string();
            Ok((code_only, true))
        }
        _ = tokio::time::sleep(Duration::from_secs(120)) => {
            bail!("Authentication timed out after 120 seconds")
        }
    }
}

View on GitHub (pinned to b0637c97ec)