Skip to content

fix(ai,coding-agent): address post-merge review of Anthropic fallback credit - #15702

Merged
H4vC merged 2 commits into
can1357:mainfrom
H4vC:fix/fallback-credit-review-nits
Oct 14, 2026
Merged

H4vC merged 2 commits into
can1357:mainfrom
H4vC:fix/fallback-credit-review-nits

Conversation

@H4vC

@H4vC H4vC commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

What

folowup to pr #13157

Fixes the post-merge review findings on the Anthropic fallback-credit redemption:

  • Only a refusal stop captures a credit handle, so the frozen request body and fct_ token stay out of session files (refusal turns are never persisted).
  • The beta-header check now skips adding fallback-credit-2026-06-01 when any fallback-credit-* beta is already present (the duplicated -06-01 check was dead code).
  • formatEchoedRefusalContent returns typed ContentBlockParam[] and drops blocks with no wire form (including unsigned thinking) instead of passing internal blocks through.
  • The redemption ladder triggers on AIError.status(error) === 400 instead of any error text containing 400.
  • AnthropicFallbackCreditHandle.params is MessageCreateParamsStreaming; the as / as unknown as casts and options!.fallbackCreditRedemption! are gone. A fallbackCreditRequestBody helper replaces the two hand-built frozen bodies.
  • The client-side redemption window is the named FALLBACK_CREDIT_TTL_MS, with a link to the docs.
  • TurnRecovery queues the redemption only when edit mode, system prompt, and tool list are all unchanged after the fallback switch, not just edit mode. The fallbackCreditHandle! assertion is removed.
  • The stored redemption turn keeps the refused model's signed thinking, now documented: a tool loop needs the turn to open with its thinking block, and a later signature rejection is recovered by the existing invalid-signature retry. The test now pins that the replayed turn opens with the signed thinking.

Why

Post-merge review of #13157. On the open question about replaying refused-model signatures: an earlier revision of this PR stored that thinking unsigned, but review pointed out that a redemption turn ending in tool_use would then replay demoted text ahead of the thinking, which Anthropic rejects with a 400 nothing retries. Keeping the signature leaves only the failure the code already recovers from.

Not addressed: packages/catalog/CHANGELOG.md has no entry for #13157's catalog changes, but that release section is already published and immutable.

Known remaining gap (pre-existing in #13157): a candidate that can redeem credit skips the same-provider signed-thinking check. If redemption is then cancelled because the switch rebuilt the prompt or tools, that request replays the refused model's signatures without a token, and the existing signature-rejection retry recovers in one extra round trip. The broader guard makes this path more common than the edit-mode-only check did.

Testing

  • bun run check:ts passes (oxlint, oxfmt, types in all packages). No Rust changed.
  • packages/ai: bun test anthropic, 639 pass.
  • packages/coding-agent: agent-session-retry-fallback.test.ts, 125 pass.
  • New and changed tests fail on main and pass with this change:
    • a 529 error whose text mentions 400 keeps the continuation shape and token instead of stepping down;
    • a frozen fallback-credit-2026-07-01 beta header is sent without -06-01;
    • unsigned thinking is omitted from the echoed continuation prefix;
    • redemption is not queued when the fallback switch rebuilds the system prompt (parameterized alongside the existing redemption test).
  • No live Anthropic run; a real Fable → Opus refusal is still unverified.

  • bun check passes
  • Tested locally
  • CHANGELOG updated with the required attribution (if user-facing; internal issue fixes use issue links, external contributions add the PR link and contributor credit after creation)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T13:13:35.875548Z 1217b23 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@roboomp roboomp added agent Agent runtime planning and orchestration fix provider:anthropic Provider-specific issues for anthropic providers LLM provider-specific issues review:p1 triaged labels Oct 14, 2026

@roboomp roboomp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @H4vC, this is a tidy follow-up to #13157. Ranked P1: one fix, scoped to that feature, and the new tests fail on main and pass here. I ran the three changed packages/ai test files locally: 86 pass. The coding-agent test could not run here because the generated tool-views.generated.js is missing.

  • Correct as claimed: AIError.status(...) === 400 replaces the /\b400\b/ text match; the fallback-credit-* prefix guard; the typed AnthropicFallbackCreditHandle.params with the casts removed; the fallbackCreditRequestBody dedupe; token capture only when stopDetails.type === "refusal" (refusals are already persist-skipped); and the system-prompt/tools/edit-mode guard in TurnRecovery.
  • Should-fix: the changelog entries credit #13157 rather than this PR and contributor (inline comment).
  • Maintainer call: storedRefusalPrefix stores the prefix thinking unsigned. That trades a signature rejection the code already recovers from for a possible leading-text 400 in tool loops, which nothing retries (inline comment, unverified live). @can1357 should confirm which failure mode is preferable.

Comment thread packages/ai/src/providers/anthropic.ts Outdated
Comment on lines +1627 to +1632
function storedRefusalPrefix(content: AssistantMessage["content"]): AssistantMessage["content"] {
const stored: AssistantMessage["content"] = [];
for (const block of refusalContinuationPrefix(content)) {
if (block.type === "redactedThinking") continue;
if (block.type === "thinking") {
stored.push({ type: "thinking", thinking: block.thinking });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question for maintainer (behaviour trade-off, non-blocking): storedRefusalPrefix turns a known, recoverable failure into an unknown one.

  • Before: the stored turn replayed the refused model's signed thinking. If the server rejected it on a tokenless turn, the error matched INVALID_THINKING_SIGNATURE_PATTERN (anthropic.ts:1952), and the existing signature retry recovered in one round trip.
  • After: the prefix thinking is stored with no signature. convertAnthropicMessages (anthropic.ts:5842-5871) then demotes it to text, or drops it.

If the redemption response continues into a tool_use, the next request (the one carrying the tool_result) replays an assistant turn that starts with demoted text, not with a thinking block. With thinking enabled, Anthropic documents a 400 for that case: "a final assistant message must start with a thinking block". [INFERENCE: not verified live for Opus 4.8 / adaptive thinking.] No retry pattern in this file matches that error, so the PR body's "cost is a prompt-cache miss" may understate the risk in a tool loop. The added test covers only a text continuation (anthropic-stream-envelope.test.ts, the "stores the refused thinking unsigned" case), not one that ends in tool_use.

Your PR body already raises this as an open question to @can1357. A maintainer should pick a side before merge.

Comment thread packages/ai/CHANGELOG.md Outdated
Comment on lines +8 to +11
- Fixed Anthropic fallback-credit redemption stepping down or dropping its credit token on errors that only mention `400` in their text, such as an overload message ([#13157](https://github.com/can1357/oh-my-pi/pull/13157))
- Fixed Anthropic fallback-credit redemption adding the `fallback-credit-2026-06-01` beta beside a newer `fallback-credit-*` beta the refused request already sent ([#13157](https://github.com/can1357/oh-my-pi/pull/13157))
- Fixed turns after an Anthropic fallback-credit redemption replaying the refused model's signed thinking, which the fallback model could reject without the credit token; the echoed thinking is now kept unsigned and refused-model redacted thinking is dropped ([#13157](https://github.com/can1357/oh-my-pi/pull/13157))
- Fixed the Anthropic fallback-credit continuation sending unsigned thinking blocks in the echoed refusal prefix, which the API rejects ([#13157](https://github.com/can1357/oh-my-pi/pull/13157))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should-fix (convention): This is an external contribution, so per AGENTS.md § Changelog these entries should be attributed to this PR, ([#15702](https://github.com/can1357/oh-my-pi/pull/15702) by [@H4vC](https://github.com/H4vC)), not #13157. Line 10 also goes into root cause ("which the fallback model could reject without the credit token; the echoed thinking is now kept unsigned..."). Changelog entries should say what the user sees; put the mechanism in the commit or PR. The same attribution fix applies to packages/coding-agent/CHANGELOG.md:22.

@H4vC
H4vC merged commit c7b809a into can1357:main Oct 14, 2026
16 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Agent runtime planning and orchestration fix provider:anthropic Provider-specific issues for anthropic providers LLM provider-specific issues review:p1 triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants