Say which upstream status a provider failure carried

0f60dffa6014 · AtlantisPleb · · parent a85feca9ab56

Say which upstream status a provider failure carried

A failed proxy call reported `{"code":"provider_failed","reason":
"http_status"}` and nothing else. That names the shape of the failure
and not the failure: a wrong credential, a rate limit, and an outage
all arrive identically, and an operator has to bisect across lanes to
learn which one they have. That is exactly how last night's dead
default model was found, and it should not have taken that.

OperationalLog.status/1 returns the one bounded, content-free number a
failure carries — a plain upstream HTTP status, and nil for anything
else. The refusal path logs it as `upstream_status=` and includes it
in the body, so a client sees `401` rather than inferring from
silence.

The redaction boundary is unchanged and the tests hold it there: a
status is a status line, not provider content, so `{:http_status,
429}` passes and a string status, a map detail, and any number outside
100..599 all return nil. A detail that could carry a prompt, a key, or
a body still never reaches a log or a receipt.

Health-aware availability in the catalog is the other half of #238 and
is not here: a lane whose calls all fail still reports itself
available.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GoYpb8FEmdxVErsv7ABCYi
Co-Authored-By
Claude Fable 5 <noreply@anthropic.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

pushed
by user · WAL seq 353 · 2026-08-25T07:46:40.516249Z

Changed files

  • modified lib/openagents/operational_log.ex
  • modified lib/openagents_web/controllers/inference_proxy_controller.ex
  • modified test/openagents/operational_log_test.exs

Diff

3 files changed, +48 -2

lib/openagents/operational_log.ex modified +18

@@ -12,5 +12,23 @@ defmodule OpenAgents.OperationalLog do

12 12
13 13
  def code(_reason), do: "other"
14 14
15
  @doc """
16
  The one bounded, content-free number a failure carries, when it has one.
17
18
  A code alone says a call failed; it cannot say whether the credential is
19
  wrong, the account is rate limited, or the provider is down — and those
20
  need different responses from an operator. An upstream HTTP status is the
21
  smallest thing that distinguishes them, and it is a status line rather than
22
  provider content, so it crosses the same boundary the code does.
23
24
  Anything that is not a plain HTTP status is `nil`: a detail that could carry
25
  a prompt, a key, or a body never reaches a log or a receipt through here.
26
  """
27
  @spec status(term()) :: pos_integer() | nil
28
  def status({_tag, status}) when is_integer(status) and status >= 100 and status <= 599,
29
    do: status
30
31
  def status(_reason), do: nil
32
15 33
  defp bounded(value), do: String.slice(value, 0, 64)
16 34
end
lib/openagents_web/controllers/inference_proxy_controller.ex modified +12 -2

@@ -204,8 +204,13 @@ defmodule OpenAgentsWeb.InferenceProxyController do

204 204
        usage = usage_of(events)
205 205
        if usage != %{}, do: meter(grant, usage)
206 206
        class = OpenAgents.OperationalLog.code(reason)
207
        Logger.warning("inference_proxy_failed code=#{class}")
208
        refuse(conn, {:provider_failed, class})
207
        status = OpenAgents.OperationalLog.status(reason)
208
        Logger.warning(
209
          "inference_proxy_failed code=#{class}" <>
210
            if(status == nil, do: "", else: " upstream_status=#{status}")
211
        )
212
213
        refuse(conn, {:provider_failed, class, status})
209 214
    end
210 215
  end
211 216

@@ -368,6 +373,11 @@ defmodule OpenAgentsWeb.InferenceProxyController do

368 373
  # the server logs. Withholding it left the caller with "the model provider
369 374
  # failed" and nothing to act on, which reads as the product being broken
370 375
  # rather than as a call that ran out of context or hit a rate limit.
376
  defp error_for({:provider_failed, class, status}) do
377
    body = %{"code" => "provider_failed", "reason" => class}
378
    {502, if(status == nil, do: body, else: Map.put(body, "upstream_status", status))}
379
  end
380
371 381
  defp error_for({:provider_failed, class}) do
372 382
    {502, %{"code" => "provider_failed", "reason" => class}}
373 383
  end
test/openagents/operational_log_test.exs modified +18

@@ -10,4 +10,22 @@ defmodule OpenAgents.OperationalLogTest do

10 10
    assert OperationalLog.code(%RuntimeError{message: "credential sentinel"}) == "runtime_error"
11 11
    assert OperationalLog.code("raw failure with a token") == "other"
12 12
  end
13
14
  describe "status/1" do
15
    test "surfaces a plain upstream HTTP status" do
16
      assert OperationalLog.status({:http_status, 429}) == 429
17
      assert OperationalLog.status({:http_status, 401}) == 401
18
      assert OperationalLog.status({:anything, 503}) == 503
19
    end
20
21
    test "refuses anything that is not a plain HTTP status" do
22
      # A detail that could carry a prompt, a key, or a body never gets through.
23
      assert OperationalLog.status({:http_status, "429 Too Many Requests"}) == nil
24
      assert OperationalLog.status({:provider_error, %{"key" => "sk-secret"}}) == nil
25
      assert OperationalLog.status({:http_status, 99}) == nil
26
      assert OperationalLog.status({:http_status, 600}) == nil
27
      assert OperationalLog.status(:timeout) == nil
28
      assert OperationalLog.status("boom") == nil
29
    end
30
  end
13 31
end

This page updates live while a promote is in flight · changelog