Skip to content

Don't treat capacity phrases in model output as provider failures - #4

Open
rayan-mazouz wants to merge 2 commits into
devasheeshG:mainfrom
rayan-mazouz:fix/sse-failure-detection
Open

rayan-mazouz wants to merge 2 commits into
devasheeshG:mainfrom
rayan-mazouz:fix/sse-failure-detection

Conversation

@rayan-mazouz

Copy link
Copy Markdown

Note: this PR was vibe coded.

Summary

Capacity phrases ("at capacity", "try a different model", "service exhausted", "temporarily overloaded") were matched anywhere in a response, including the model's own text:

  • Prefetch: the phrase check ran before the output check. A healthy stream whose first deltas mentioned one of those phrases was discarded, the account was put on cooldown, and the request was replayed on another account, spending quota twice.
  • Mid-stream: a text delta containing one of the phrases ended the response early.
  • Non-streaming: a message whose text mentioned one triggered failover.

Separately, a real provider error that arrived after streaming had started was replaced with a synthetic message_stop. Claude Code therefore got a truncated response, possibly mid tool call, that looked like a normal completion, and it never retried.

Commits

  • fix(proxy): ignore capacity phrases inside model output. Failures are now classified per SSE frame. A frame is a provider error only if it has an error marker and no output event, and it's a capacity error only if that error frame contains a capacity phrase. A non-streaming body that is an Anthropic message is never an error. Error frames that arrive before any output still fail over to the next account, exactly as before.
  • fix(proxy): forward mid-stream provider errors to the client. After output has reached the client, failover is impossible anyway, so provider error frames are passed through unchanged and the client can surface the failure and retry. The stream is still re-chunked into whole frames for the model-name rewrite.

Testing

New tests cover:

  • streamed and non-streaming model output that mentions capacity phrases (served by the first account, no cooldown);
  • an error frame in the prefetch window (still fails over, and is classified as a capacity error);
  • a mid-stream error frame (forwarded unchanged).

These tests fail on main. The full suite passes (155).

Capacity phrases ("at capacity", "try a different model", ...) were
matched anywhere in a response, including the model's own text:

- during SSE prefetch the phrase check ran before the output check, so a
  healthy stream whose first deltas mentioned one was discarded, the
  account was cooled down, and the request was replayed on another
  account, spending quota twice;
- mid-stream, a text delta containing one ended the response early;
- a non-streaming message whose text mentioned one triggered failover.

Classify failures per SSE frame instead: a frame is a provider error only
when it carries an error marker and no output event, and it is a capacity
error only when that error frame contains a capacity phrase. Non-streaming
bodies that are Anthropic messages are never errors. Error frames that
arrive before any output still fail over to the next account.
Once a stream had started, an upstream error event was replaced with a
synthetic message_stop, so Claude Code received a truncated response
(possibly mid tool call) reported as a normal completion and never
retried. Buffered non-streaming SSE bodies were rewritten the same way.

Failover is only possible before output reaches the client, which the
prefetch already handles. After that, pass provider error frames through
unchanged so the client sees the failure and can retry. The stream is
still re-chunked into whole frames for the model-name rewrite.
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