[BUG] `claude gateway`: Bedrock leg sanitizes 400 bodies for non-CRI callers without capability classification, disabling reactive compaction
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Over-context requests through claude gateway on a Bedrock upstream reach the client as an error it can't recognise, which disables reactive compaction. The session is then unrecoverable — /compact fails identically; only /rewind or /clear works.
Mechanism. The cloud-SDK leg replaces the upstream body with generic status-keyed copy (400 → "upstream rejected the request") for every caller, preserving the original message internally. The step that classifies that message and emits capability_rejected: prompt_too_long runs only for CRI callers. Sanitization and classification were designed as a pair; on the ordinary gateway login flow, only sanitization applies. The client matches overflows on either the upstream wording or the sentinel, gets neither, and never enters the recovery path.
Not a wording-coverage gap. Bedrock sends prompt is too long: <N> tokens > <limit> maximum — already matched by the classifier, and parseable for the token gap that sizes the precompute borrow. Both were on the wire; both were dropped before the client saw them.
Effect. The conversation keeps growing across repeated failures, with no compaction attempted and no indication to the user that the problem is context length.
Two notes:
- 2.1.229's fix is suppressed by this. "Improved 'prompt is too long' errors to explain why automatic compaction could not recover" only generates once the client has recognised the overflow, so it can never fire for gateway users.
- Your operator contract already requires the missing half — classify before replacing a
400/413body and emitcapability_rejected: <class>"so the session self-heals instead of stranding", explicitly extended to a gateway authoring the body itself on a cloud-SDK leg.
What Should Happen?
Either classify for every caller whose body is replaced — the upstream message is already preserved for exactly this — or stop replacing the body for non-CRI callers, since sanitization is defined as a CRI obligation and applying it without classification is what removes recovery.
Error Messages/Logs
Same request, correlated by the gateway's `request_id`:
# gateway log (server side)
warn bedrock upstream 400 request_id=<id>: 400 prompt is too long: <N> tokens > <limit> maximum
# client transcript, same request_id
API Error: 400 upstream rejected the request
Steps to Reproduce
- Run
claude gatewaywith abedrockupstream. - Log a client in via the standard gateway login flow — not a CRI principal. This is load-bearing; a CRI caller will not reproduce it.
- Pick a model whose Bedrock ceiling you can exceed (e.g. an Opus
[1m]variant, 1,000,000 tokens) and drive the context past it. - Gateway log shows the real message; client shows
API Error: 400 upstream rejected the request. - No compaction is attempted, and
/compactcannot recover the session.
Expected at 4: capability_rejected: prompt_too_long, or the upstream wording passed through.
Expected at 5: the client enters its recovery path — borrow or await a precomputed summary and swap it in locally (summary plus messages after the precompute boundary, built while the context still fit, so no over-limit request). To be precise about the guarantee: if no precomputed summary exists, the cold fallback re-sends the full history and fails too. Correct classification doesn't guarantee recovery; it guarantees the swap path is reachable, and that when recovery is impossible the user gets 2.1.229's accurate explanation instead of an error naming no cause.
To isolate the gateway in one step: the same client pointed directly at Bedrock recognises the error and recovers, because it sees the upstream wording.
Claude Model
Opus
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
Client 2.1.231 (Claude Code); gateway binary 2.1.220. Confirmed unchanged in 2.1.231, so not fixed on latest.
Platform
AWS Bedrock
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
Related: #86500 — please don't close either as a duplicate of the other. That issue covers a 1,000,000-token window resolving with source auto on non-first-party providers, which leaves the context cap unenforced. #86500 is the cause — it's what lets a session reach the ceiling. This issue is what makes the failure silent and unrecoverable once it arrives. Fixing only this one means sessions still walk into the ceiling, just with an accurate message; fixing only #86500 means they stop arriving, but any that do still strand. They also have different owners: #86500 is client-side window resolution, this one is the gateway's error path.
One diagnostic gap. The cloud-SDK leg logs no classification outcome, while the Anthropic passthrough leg logs the class it resolved or no capability class when it resolved none. Without an equivalent line, an operator gets no signal that classification was skipped. Combined with the real message being logged server-side but withheld from the user, neither side can diagnose this: the user can't tell it's a context-length problem, and the operator can't tell the client's recovery was disabled.