zed-industries/zed · warning
unexpected path in OAuth callback: {}
Error message
unexpected path in OAuth callback: {} What it means
The tiny_http callback server binds a loopback port and accepts only requests whose URL path equals the unique expected callback path. Url::parse succeeds but any other path bails with the actual path seen. A dedicated /cancel path and 404s for random scanners are handled separately; this error is for paths that parsed but did not match.
Source
Thrown at crates/oauth_callback_server/src/oauth_callback_server.rs:358
}
let _ = tx.send(result);
return;
}
});
Ok((redirect_uri, rx))
}
fn handle_oauth_callback_request(
request: &tiny_http::Request,
expected_path: &str,
) -> Result<OAuthCallbackParams> {
let url = Url::parse(&format!("http://localhost{}", request.url()))
.context("malformed callback request URL")?;
if url.path() != expected_path {
anyhow::bail!("unexpected path in OAuth callback: {}", url.path());
}
let query = url
.query()
.ok_or_else(|| anyhow!("OAuth callback has no query string"))?;
OAuthCallbackParams::parse_query(query)
}
/// Callback path reserved for evicting a previously-running OAuth callback
/// server bound to the same port. Always handled, regardless of `config.path`.
const CANCEL_PATH: &str = "/cancel";
const BIND_MAX_ATTEMPTS: u32 = 10;
const BIND_RETRY_DELAY: Duration = Duration::from_millis(200);
const CANCEL_REQUEST_TIMEOUT: Duration = Duration::from_secs(2);
fn bind_callback_server(config: &OAuthCallbackServerConfig) -> Result<tiny_http::Server> {
// Ephemeral ports always succeed; skip the cancel-retry dance entirely.View on GitHub (pinned to f4178619ac)
Solutions
- Retry the sign-in from the app — each flow generates a fresh path/port
- Close stale browser tabs or history autocomplete entries pointing at old localhost callback URLs
- If a local scanner/AV probes loopback ports, exclude the callback port range or disable the probe during sign-in
Defensive patterns
Strategy: validation
Validate before calling
fn path_matches(expected: &str, request_url: &str) -> bool {
Url::parse(&format!("http://localhost{request_url}"))
.map(|u| u.path() == expected)
.unwrap_or(false)
} Type guard
fn is_expected_callback_path(url: &Url, expected: &str) -> bool {
url.path() == expected
} Try / catch
match handle_oauth_callback_request(&request, expected_path) {
Err(e) if e.to_string().contains("unexpected path") => {
respond_404(); // ignore probes/stale tabs; keep server alive for the real callback
}
r => r?,
} Prevention
- Only ever open authorize URLs freshly generated for the current server instance
- Don't bookmark or replay localhost OAuth callback URLs
- Ignore/log-and-continue unmatched paths instead of tearing down the flow
- If local security tools scan loopback ports, expect occasional stray requests and tolerate them
When it happens
Trigger: Something other than the provider redirect hits the callback port: a browser prefetch/probe of localhost, an antivirus or local agent scanning ports, a stale browser tab replaying an old callback URL from a previous sign-in attempt (whose path differed), or a second concurrent flow targeting the same port.
Common situations: Browser history/autocomplete firing an old OAuth callback URL; localhost port-scanning tools; retrying an old authorization link after the server restarted with a new path.
Related errors
- didn't receive login redirect
- unsupported MCP url scheme {}
- OAuth endpoint must use HTTPS (got {}://{})
- OAuth endpoint must not point to private/reserved IP: {}
- OAuth endpoint must not point to private/reserved IP: ::ffff
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/78403f0f28bd51e9.
Report an issue: GitHub.