Skip to content

hook: a 4xx with a decision body is a verdict, not an outage - #16

Merged
davidcrowe merged 1 commit into
mainfrom
fix/429-is-a-verdict
Aug 17, 2026
Merged

hook: a 4xx with a decision body is a verdict, not an outage#16
davidcrowe merged 1 commit into
mainfrom
fix/429-is-a-verdict

Conversation

@davidcrowe

Copy link
Copy Markdown
Collaborator

What

Every non-2xx from /govern/tool-use routed to the outage posture (failPostureOnOutage). But the gateway's 429 rate-limit deny and 400 invalid-tool deny are deliberate verdicts carrying {decision, reason} — treating them as outages meant:

  • interactive: fail-open → the server's rate limit was simply unenforced client-side, with a scary-but-wrong UNGOVERNED banner
  • unattended tiers: deny, but with a misleading "gateway unreachable" message instead of the real reason (which tells the agent to wait and retry)

Now: 4xx → try to parse the body; denydenyByPolicy with the server's reason; ask → the ask path. Bodyless 4xx (auth guard's 401) and all 5xx keep the existing outage posture, unchanged.

This is the correctness half of #553 — transparent absorption of short retry-afters can build on this (the reason string now reaches the agent intact).

Tests

New test/429-is-a-verdict.test.mjs (same stub-gateway harness as the #690 retry test): 429-blocks-interactive, 429-real-reason-unattended, 400-verdict, 401-keeps-outage-posture, 503-keeps-outage-posture. Verified red on the unfixed hook (3/5 fail); full suite 66/66 green with the fix.

Refs gatewaystack-connect#718, gatewaystack-connect#719, gatewaystack-connect#553.

🤖 Generated with Claude Code

The gateway answers rate-limit denies (429) and invalid-tool denies (400)
with {decision, reason}, but every non-2xx routed to the outage posture —
so interactive sessions failed OPEN on a deliberate deny (rate limits were
unenforced client-side) and unattended tiers denied with a misleading
"gateway unreachable" message. 4xx responses now parse the body first:
deny → denyByPolicy with the server's reason, ask → the ask path. Bodyless
4xx (the auth guard's 401) and all 5xx keep the outage posture unchanged.

Refs gatewaystack-connect#718, #719, #553.
@davidcrowe
davidcrowe merged commit fb12fc7 into main Aug 17, 2026
2 checks passed
@davidcrowe
davidcrowe deleted the fix/429-is-a-verdict branch August 17, 2026 06:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant