feat(proxy): keep a Claude session working on a fallback model when Anthropic flags a request - #260
Open
marcelstenkewitz wants to merge 1 commit into
Open
marcelstenkewitz wants to merge 1 commit into
marcelstenkewitz wants to merge 1 commit into
Conversation
…nthropic flags a request When Anthropic refuses a streamed request, retry it on a configured fallback before content, or move later requests after content. Remember the choice per Claude session and requested model, including non-streaming requests and token counts. The feature is off by default and ships without fallback rules. The response hold releases after 64 KiB of pre-content events, and route ids are capped in logs. Tests use a fake Anthropic origin and fake fallback adapter. They do not cover a real flag or a real provider. Co-Authored-By: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes for users
I built this for my own Clodex setup: when Anthropic flags a request, I wanted the current Claude Code session to keep working through one of my configured fallback models instead of stopping. I figured I would throw the generic mechanism into a PR in case it is useful upstream. It is disabled unless a user configures fallback rules, and it ships with no rules or provider defaults. Enabling a rule means the conversation Anthropic refused — the whole payload, unmodified — is sent to the fallback provider the user configured. Clodex does not enforce that provider's retention or training policy.
Problem and root cause
In proxy mode, a normal Claude-model request is passed through to Anthropic as raw bytes. Anthropic can return HTTP 200 and finish the stream with
stop_reason: "refusal"; Clodex observed usage beside that stream but had no refusal hook, so Claude Code received the refusal and the session remained on the same model.The path is reachable in a normal
clodex claudesession using Anthropic passthrough plus a configured OpenAI-compatible route. Historical Claude Code transcript rows show both timing shapes: refusals before content, one after a token, and others after 190–6,180 output tokens. That requires two behaviors: retry the current request only before content, but remember every refusal for later requests.The change
flagFallbackrule list in~/.clodex/config.json. A route may be a saved alias or a full Clodex route id. No rules are supplied by default.count_tokensrequests use the remembered route./clearchanges the session id; a proxy restart clears the in-memory map.flag_switchlifecycle row with the tier, category, model, route, and unavailable-route state.Deliberately left out: refusal detection in non-streaming response bodies, Anthropic 4xx policy errors, and
count_tokensresponses; persisted switch state; a CLI forflagFallback; fallback providers or prices; automatic return to Anthropic; and any default routing policy. No rules or provider defaults ship, and the example route names are illustrative.This is larger than a focused fix and I did not open an issue first. I am happy to close or reshape it if this is not a direction Clodex wants.
Evidence
pnpm typecheck && pnpm test && pnpm build— Node 24.14.1, pnpm 10.34.5, freshCLODEX_HOME, proxy and Anthropic override variables unset: 125 test files passed / 2 skipped; 2,930 tests passed / 20 skipped; build succeeded.mainwhile retaining the six complete changed test files, 16 tests failed and 83 passed. Failures covered rule loading/validation, route override, tier 1, tier 2, session memory, unavailable routes, fallback errors, token counts, gzip, the 64 KiB ceiling, and route-log capping.flag_switchrow, retried through a configured Kimi route, returned HTTP 200, and completed with output. The refusal categories werecyberandreasoning_extraction.CLODEX_LOG_REQUEST_PREVIEWwas unset.unrecognized_modelnotice before returning output.origin/mainremainsf1c74ac;git merge-treeis clean; open PR fix(models): let you raise the context window on a model whose provider publishes none #259 touches context-window and registry files but no file in this diff, so there is no land-order constraint.Automated server tests use a fake Anthropic origin and fake fallback adapter. The real checks above cover tier 2 and normal traffic, not a real mid-stream tier-1 refusal, a provider timeout, Windows, or a process restart followed by
--resume. CI must supply the Windows leg. Earlier acceptance also ran the full gate under Node 22.19.0; the current pre-PR gate used the repository-pinned Node 24.14.1.Failure and rollback behavior
A rule naming an unavailable route passes the original refusal through and records
unavailable: true. A selected fallback's error reaches the client; the flagged content is not sent back to Anthropic. Malformed saved rules stop proxy startup with a configuration error rather than silently changing routing.The feature persists no new state and needs no migration. Removing
flagFallback(or making it empty) restores ordinary pass-through behavior; restarting the proxy clears remembered switches. Installing an upstream build without this change also removes the feature while leaving the unknown config key inert.🤖 Generated with Claude Code
https://claude.ai/code/session_01UE5y6RAFZ6Jqk33sk3EKrq