Kuberwastaken/claurst · error · anyhow::Error
Invalid HTTP request
Error message
Invalid HTTP request
What it means
The loopback HTTP server accepted the browser's OAuth callback connection but the first request line could not be parsed as `GET /auth/callback?...`. Means the client sent a malformed or non-HTTP request (e.g. a health probe, prefetch, or telnet-style connection) to the local callback port.
Solutions
- Retry the login flow; the browser usually sends a well-formed request on the next attempt
- Ensure nothing else (proxy, antivirus, another app) is hitting 127.0.0.1 on the callback port
- If it persists, capture the raw request line to identify what client is connecting
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at src-rust/crates/cli/src/codex_oauth_flow.rs:130 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/529374c039907f13.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/cli/src/codex_oauth_flow.rs:130
async fn wait_for_callback(listener: TcpListener) -> anyhow::Result<(String, String)> {
use tokio::io::AsyncWriteExt;
let (mut socket, _) = tokio::time::timeout(
std::time::Duration::from_secs(300), // 5 minute timeout
listener.accept(),
)
.await
.map_err(|_| anyhow!("OAuth callback timeout (5 minutes)"))?
.map_err(|e| anyhow!("Failed to accept connection: {}", e))?;
let mut reader = BufReader::new(&mut socket);
let mut request_line = String::new();
reader.read_line(&mut request_line).await?;
// Parse "GET /auth/callback?code=...&state=... HTTP/1.1"
let parts: Vec<&str> = request_line.split_whitespace().collect();
if parts.len() < 2 {
bail!("Invalid HTTP request");
}
let path = parts[1];
let query_start = path.find('?').ok_or_else(|| anyhow!("No query string in callback"))?;
let query = &path[query_start + 1..];
let mut code = String::new();
let mut state = String::new();
let mut error = String::new();
for param in query.split('&') {
let kv: Vec<&str> = param.splitn(2, '=').collect();
if kv.len() == 2 {
match kv[0] {
"code" => code = urlencoding::decode(kv[1])?.to_string(),
"state" => state = urlencoding::decode(kv[1])?.to_string(),
"error" => error = urlencoding::decode(kv[1])?.to_string(),
"error_description" => error = urlencoding::decode(kv[1])?.to_string(),
View on GitHub (pinned to b0637c97ec)