Hmbown/CodeWhale · warning

OAuth callback path was not

Error message

OAuth callback path was not {}

What it means

After method validation, the callback's request target path must equal the provider's configured `callback_path`. This throw means a request reached the loopback listener whose path is not the registered OAuth redirect path, so it cannot be the expected provider callback.

Solutions

  1. Use the complete redirect URL exactly as issued (including path and query)
  2. Ensure `callback_path` in provider params matches the redirect_uri registered with the provider
  3. Ignore/whitelist benign requests like /favicon.ico from touching the listener path check
  4. Restart the flow if the redirect URL was edited

Example fix

// before (edited URL)
http://127.0.0.1:8765/?code=abc
// after (exact callback path preserved)
http://127.0.0.1:8765/oauth/callback?code=abc
Defensive patterns

Strategy: validation

Validate before calling

let path = target.split('?').next().unwrap_or(target);
assert_eq!(path, expected_callback_path, "unexpected callback path: {path}");

Type guard

fn target_matches_path(target: &str, expected: &str) -> bool {
    target.split('?').next().unwrap_or(target) == expected
}

Prevention

When it happens

Trigger: `query_from_target` sees a target whose path (before '?') differs from `params.callback_path` — e.g. `/` from a browser hitting the root, `/favicon.ico`, or a callback path changed in provider params after the auth URL was built.

Common situations: User truncates the redirect URL in the address bar; a browser prefetches /favicon.ico on the port; provider redirect URI configured inconsistently between the auth request and the listener.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/1fd688b36d32fb05. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/oauth.rs:1261

    }
}

fn parse_http_request_target(request_line: &str) -> Result<String> {
    let mut parts = request_line.split_whitespace();
    let method = parts.next().unwrap_or_default();
    anyhow::ensure!(
        method.eq_ignore_ascii_case("GET"),
        "OAuth callback must be GET"
    );
    let target = parts
        .next()
        .context("OAuth callback missing request target")?;
    Ok(target.to_string())
}

fn query_from_target<'a>(params: &OAuthProviderParams, target: &'a str) -> Result<&'a str> {
    let path = target.split('?').next().unwrap_or(target);
    anyhow::ensure!(
        path == params.callback_path,
        "OAuth callback path was not {}",
        params.callback_path
    );
    Ok(target.split_once('?').map(|(_, q)| q).unwrap_or(""))
}

/// Bind the loopback callback on both IP stacks for the first free port.
///
/// The redirect URI has to say `localhost` — that is what is registered with
/// the authorization server, and redirect matching is exact — but `localhost`
/// resolves to `::1` before `127.0.0.1` on IPv6-first hosts. Binding only
/// IPv4 left the browser connecting to a closed port, which browsers paper
/// over with Happy Eyeballs fallback: a working sign-in becomes a slow one,
/// and a broken one wherever that fallback is disabled. Binding both is the
/// fix that keeps the registered redirect URI intact.
///
/// A host with only one stack available binds only that one and still works.

View on GitHub (pinned to 433685b202)