Skip to content

fix(provider): stop refused Anthropic tool turns without retries - #500

Open
haohaomin wants to merge 1 commit into
leookun:mainfrom
haohaomin:codex/upstream-anthropic-refusal
Open

haohaomin wants to merge 1 commit into
leookun:mainfrom
haohaomin:codex/upstream-anthropic-refusal

Conversation

@haohaomin

Copy link
Copy Markdown

When Anthropic returns stop_reason: "refusal" alongside streamed tool blocks, the turn must stop without executing those tools or automatically requesting the same refused turn again. This change carries a typed refusal through the provider, model cycle, run failure, recorder, and Cursor error response, so it is non-retryable at both the server and client boundaries.

This builds on the refusal precedence fix proposed by @kevin9327 in #423. That proposal correctly prevents tool execution by producing a finish-reason disagreement, but the existing run loop treats that protocol failure as retryable. In our earlier controlled end-to-end test of that proposal, a persistent refusal caused 9 requests over approximately 40 seconds. The typed failure in this PR ends after one request and emits no Cursor execution messages.

Both message_stop and the EOF fallback use the same finish classification. A refusal stays recorded even if a later delta reports another stop reason. Plain-text refusals still finish normally; ordinary tool calls, length-limit failures, partial text/thinking, token usage, and transient-error retries retain their existing behavior. Recorded refusals use the existing provider error category; no database changes are needed.

Validation:

  • Eight HTTP/SSE integration tests exercise the actual provider/model-cycle path and a ProviderRouter → Run → Cursor protocol path with SQLite. They cover refusal, EOF, ordinary tool calls, text-only refusal, retained partial output/usage, and the client error details.
  • The full protocol test asserts one HTTP request, zero execution messages, recorded error/usage, is_retryable=false, and immediate error display.
  • Earlier manual validation in real Cursor against a controlled local Anthropic SSE server confirmed refused tools were skipped without execution or retries, plain-text refusal completed, and ordinary shell tools ran successfully. This was a simulated provider response, not a live Anthropic refusal.

Only the server fix and regression tests are included. This PR is independent of #487 and contains no fork customization or release changes. Related: #423; it does not require that PR to be merged first.

Revalidated on current upstream main (9a8fde2): cargo test -p cursor-server --all-targets passed all 267 tests; server all-targets Clippy with -D warnings, cargo fmt --all -- --check, and git diff --check passed. This upstream-only branch contains seven production files and one integration-test file.

Build on the refusal precedence correction proposed in leookun#423. Preserve typed refusal failures through recording, run retry decisions, and Cursor error details, with HTTP/SSE integration coverage.

Co-authored-by: kevin9327 <kevin9327@users.noreply.github.com>
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