fix(anthropicapi): report stop_reason "tool_use" for tool-call responses#345
Conversation
/v1/messages returned stop_reason "end_turn" for responses that contained a tool_use block. OpenAI-family providers report finish_reason "stop" alongside tool calls when a tool is forced via tool_choice, and stopReasonFromFinish only consulted hasToolCalls in the finish_reason == "" branch, so the "stop" branch won and dropped the tool-use signal. The Anthropic Messages API contract guarantees stop_reason "tool_use" whenever the content holds tool_use blocks. Without it, Anthropic SDK tool-use loops treat the turn as finished and never execute the tool. stopReasonFromFinish now returns "tool_use" whenever the response carries tool calls, before the finish_reason switch. Also adds release E2E coverage for the Anthropic Messages API ingress endpoints (scenarios S96-S109, including the forced-tool-use case that surfaced this bug) and widens the runner's scenario-ID regex to accept 3-digit IDs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR fixes a stop-reason contract bug in the Anthropic API response handler, ensuring responses with tool calls always return ChangesTool-Use Stop-Reason Fix and E2E Validation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR fixes Anthropic Messages stop reason handling for tool-call responses. It changes:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (2): Last reviewed commit: "chore: ignore the entire .claude directo..." | Re-trigger Greptile |
| jq -e '.type == "message" and any(.content[]; .type == "text" and (.text | contains("QA_MESSAGES_ALIAS_OK")))' "$RESP_FILE" >/dev/null | ||
| curl -fsS -X DELETE "$BASE_URL/admin/aliases" \ | ||
| -H 'Content-Type: application/json' \ | ||
| -d "{\"name\":\"$MESSAGES_ALIAS\"}" >/dev/null |
There was a problem hiding this comment.
S104 creates qa-messages-alias-$QA_SUFFIX, but the cleanup only runs after the request and jq assertion succeed. The runner executes each scenario with set -euo pipefail, so if the /v1/messages call or assertion fails, this DELETE is skipped and the alias is left behind. That breaks the documented self-contained rerun behavior, especially when retrying with the same --qa-suffix or keeping artifacts.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/e2e/release-e2e-findings.md`:
- Around line 91-93: The fenced code block containing `{ "finish_reason":
"stop", "has_tool_calls": 1 }` is missing a language tag (MD040); update that
fence to include a language identifier such as `json` (or `text`) so the block
becomes a proper fenced code block with a language specifier.
- Around line 15-16: The markdown contains inline code spans with trailing
spaces in the regex examples (e.g. `^### S[0-9][0-9] ` and `^### S[0-9][0-9]+
`); remove the trailing space characters inside those backticks so the spans
become `^### S[0-9][0-9]` and `^### S[0-9][0-9]+` respectively to satisfy MD038
(no-space-in-code) while preserving the exact regex text.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: cc8d4796-68ce-47ee-8ab0-57a92cb380cb
📒 Files selected for processing (5)
internal/anthropicapi/response.gointernal/anthropicapi/response_test.gotests/e2e/release-e2e-findings.mdtests/e2e/release-e2e-scenarios.mdtests/e2e/run-release-e2e.sh
| The runner only parsed 2-digit scenario IDs (`^### S[0-9][0-9] `). It was | ||
| widened to `^### S[0-9][0-9]+ ` in `run-release-e2e.sh` so `S96`–`S109` (and |
There was a problem hiding this comment.
Remove spaces inside inline code spans in the regex examples.
The inline code spans include trailing spaces, which triggers MD038 (no-space-in-code).
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 15-15: Spaces inside code span elements
(MD038, no-space-in-code)
[warning] 16-16: Spaces inside code span elements
(MD038, no-space-in-code)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/release-e2e-findings.md` around lines 15 - 16, The markdown
contains inline code spans with trailing spaces in the regex examples (e.g.
`^### S[0-9][0-9] ` and `^### S[0-9][0-9]+ `); remove the trailing space
characters inside those backticks so the spans become `^### S[0-9][0-9]` and
`^### S[0-9][0-9]+` respectively to satisfy MD038 (no-space-in-code) while
preserving the exact regex text.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Address review comments on the findings doc: - MD040: add the json language tag to the finish_reason evidence fence. - MD038: render the runner scenario-ID patterns as the slash-delimited awk literals (/^### S[0-9][0-9] / and /^### S[0-9][0-9]+ /) so the code spans no longer have a trailing space, while keeping the regex text exact — the trailing space before the closing slash is significant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the /.claude/settings.local.json entry with /.claude/ so all user-specific Claude Code state is ignored, and move it into the existing "Others" group. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
POST /v1/messages(the Anthropic Messages API ingress endpoint) returnedstop_reason: "end_turn"for responses that contained atool_usecontentblock. The Anthropic Messages API contract guarantees
stop_reason: "tool_use"whenever the content holds
tool_useblocks — Anthropic SDK tool-use loopsbranch on it to decide whether to execute a tool and continue. With
"end_turn", such a client treats the turn as finished and never executesthe tool, breaking agentic tool-use flows through
/v1/messages.Root cause
OpenAI-family providers report
finish_reason: "stop"alongside tool callswhen a tool is forced via
tool_choice(confirmed againstgpt-4.1-nano).stopReasonFromFinishonly consultedhasToolCallsin thefinish_reason == ""branch, so the
"stop"branch won and dropped the tool-use signal.Fix
stopReasonFromFinishnow returns"tool_use"whenever the response carriestool calls, before the
finish_reasonswitch.User-visible impact
Tool-use loops through
/v1/messagesnow work with OpenAI-family providers(the common case: a forced
tool_choice). Plain text responses are unaffected.Tests
internal/anthropicapi/response_test.go:TestFromChatResponseStopReasonsgains
stop_with_tool_callsandempty_with_tool_callscases — thefinish_reason: "stop"+ non-empty tool calls combination that the previoustable never exercised.
tests/e2e/release-e2e-scenarios.md: new section 18 with 14 scenarios(
S96-S109) covering the Anthropic Messages API ingress endpoints —non-streaming, cross-provider routing, streaming SSE, polymorphic
system,multi-turn,
count_tokens, forced tool use (the case that surfaced this bug),multimodal image, alias resolution, four error-envelope negatives, and
audit-log visibility.
tests/e2e/run-release-e2e.sh: scenario-ID regex widened to accept 3-digitIDs so
S96+ parse.tests/e2e/release-e2e-findings.md: findings from the validation run.Validation
make test-raceandmake lintpass (pre-commit).the one existing miss,
S91, is a known idle-sleep test-host flake thatpasses on a clean re-run).
curlagainst a real gateway — a forced-tool-use/v1/messagesrequest returnsstop_reason: "tool_use".🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests
Documentation
Chores