From 0f00f72b478bd0e98e4c5fde2af9d5a5a9bbd476 Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Fri, 22 May 2026 07:10:39 +0300 Subject: [PATCH] fix(router,handlers): strip trailing slash from rewritten URL + log upstream failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two coupled bugs surfaced after 9b0ed0b: 1. url::Url::parse("http://host:port").to_string() normalises the empty path to "/", so rewrite_loopback_host was returning "http://beast:13131/". Downstream callers then did format!("{endpoint}/v1/chat/completions") and produced a double-slash path that neuron's axum router 404'd with an empty body. Strip the trailing slash in the rewriter so the endpoint is a clean base string for concatenation. 2. The anthropic_messages handler returned the upstream's empty body to the API caller as `"upstream error: "` with no journal log on the cortex side. Operators had no way to see what happened. Add warn-level tracing on both upstream failure paths (network error and non-2xx) with model, node, target URL, status, and a 512-char body snippet. The API response now carries just `"upstream returned "` — the implementation detail lives in the log. Updates the two existing rewrite tests for the no-trailing-slash output. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/cortex-gateway/src/handlers.rs | 23 ++++++++++++++++++++--- crates/cortex-gateway/src/router.rs | 12 +++++++++--- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/crates/cortex-gateway/src/handlers.rs b/crates/cortex-gateway/src/handlers.rs index af61a49..3c047d2 100644 --- a/crates/cortex-gateway/src/handlers.rs +++ b/crates/cortex-gateway/src/handlers.rs @@ -138,9 +138,10 @@ async fn anthropic_messages( } } else { // Non-streaming: proxy, buffer full response, translate back to Anthropic. + let target_url = format!("{}/v1/chat/completions", route.endpoint); let upstream_resp = fleet .http_client - .post(format!("{}/v1/chat/completions", route.endpoint)) + .post(&target_url) .body(openai_body) .header("content-type", "application/json") .send() @@ -150,7 +151,14 @@ async fn anthropic_messages( Ok(r) => r, Err(e) => { metrics::counter!("cortex_request_errors_total", &labels).increment(1); - return error_response(502, &format!("upstream request failed: {e}")); + tracing::warn!( + model = %model_id, + node = %route.node_name, + target = %target_url, + error = %e, + "anthropic proxy: upstream request failed (network)" + ); + return error_response(502, "upstream request failed"); } }; @@ -158,7 +166,16 @@ async fn anthropic_messages( metrics::counter!("cortex_request_errors_total", &labels).increment(1); let status = upstream_resp.status().as_u16(); let body = upstream_resp.text().await.unwrap_or_default(); - return error_response(status, &format!("upstream error: {body}")); + let body_snippet = body.chars().take(512).collect::(); + tracing::warn!( + model = %model_id, + node = %route.node_name, + target = %target_url, + status, + body = %body_snippet, + "anthropic proxy: upstream returned non-2xx" + ); + return error_response(status, &format!("upstream returned {status}")); } let body_bytes = match upstream_resp.bytes().await { diff --git a/crates/cortex-gateway/src/router.rs b/crates/cortex-gateway/src/router.rs index cc0cef5..45acfc0 100644 --- a/crates/cortex-gateway/src/router.rs +++ b/crates/cortex-gateway/src/router.rs @@ -335,7 +335,13 @@ fn rewrite_loopback_host(inference_url: &str, neuron_endpoint: &str) -> Option