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

  1. Retry the sign-in from the app — each flow generates a fresh path/port
  2. Close stale browser tabs or history autocomplete entries pointing at old localhost callback URLs
  3. 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

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


AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20). Data as JSON: /api/errors/78403f0f28bd51e9. Report an issue: GitHub.