Skip to content

fix(anthropic): replay a stream that reports overload before any output - #4085

Merged
kojiwakayama merged 10 commits into
mainfrom
fix/anthropic-sse-overloaded-retry
Aug 24, 2026
Merged

fix(anthropic): replay a stream that reports overload before any output#4085
kojiwakayama merged 10 commits into
mainfrom
fix/anthropic-sse-overloaded-retry

Conversation

@kwakayama

@kwakayama kwakayama commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

A transient Anthropic overload ends an agent run terminally instead of retrying, because the error arrives inside the SSE body rather than as an HTTP status.

Anthropic answers HTTP 200, then sends event: error / {"type":"overloaded_error"} as the first stream event. buildAnthropicSseError classifies it correctly (ProviderOverloadedError, retryable: true), but nothing acts on the flag: the only retry loop is requestStream, which hands the body off the moment headers arrive and then rethrows everything via if (streamOwnsDeadline) throw error — correct on its own terms, since a reader already owns the body.

So the replay has to live in the provider, where the request body is still available to re-issue.

What ships

continuePausedStream now replays the current request when the parse throws, under two guards:

  1. nothing was yielded for this request — a mid-turn failure can never duplicate output
  2. the failure is a typed ProviderError with retryable: true — an authentication error is not replayed, and caller cancellation surfaces as AbortError, so it cannot reach the branch

The bound (2 replays) and backoff (1s, 2s) match the pre-header loop in provider/runtime-loader/provider-http.ts. The backoff adds at most 3 seconds. Replay header attempts remain inside the shared 40-second idle-window budget, and each replay header wait is capped at 10 seconds.

The two requestStream call sites collapsed into one issueStream(requestBody) helper so the replay and the pause_turn continuation issue requests identically. That is the only refactor.

TDD

Red first — 2 of the first 4 cases failed before the change:

replays the request when the stream errors as overloaded before any output ... FAILED
does not replay once the stream has produced output ...................... ok  (held vacuously)
does not replay a non-retryable in-stream error .......................... ok  (held vacuously)
bounds the replays and surfaces the provider failure when exhausted ...... FAILED

Every guard is mutation-proven, each mutation verified to have landed before the run:

Mutation Result
delete yieldedThisAttempt || RED — does not replay once ... output
!isReplayableAnthropicStreamFailure(...) -> false RED — does not replay a non-retryable ...
MAX_ANTHROPIC_STREAM_REPLAYS 2 -> 5 RED — bounds the replays, shares one replay budget
replay body instead of requestBody RED — replays a pause_turn continuation
move streamReplayCount inside the loop RED (see caveat)

Two of those need their history stated rather than just their result.

The retryable-classification mutation was wrong the first time. Deleting the term left a dangling || and the file failed to parse, so no test ran — an empty result that reads like a pass. Redone as a false substitution, and the RED confirmed.

Replaying the wrong body originally SURVIVED. The fetch stub answered positionally and ignored what was sent, so the test could not tell the continuation from the original turn. The stub now records every request body and the pause_turn case asserts the message count of each ([1, 2, 2]); the mutation reddens it. This is why the pause_turn case exists at all.

Caveat on the last row. Moving streamReplayCount inside the loop makes the replay unbounded, and the suite hangs rather than failing an assertion. That is a real RED, but a hang is a weaker signal than a failed expectation, and it is worth knowing that this particular regression would show up as a timeout.

Green:

deno test extensions/ext-llm-anthropic/     7 passed (270 steps), 0 failed
deno check  (all 5 changed files)           clean
deno lint / deno fmt --check                clean

src/provider/ fails locally without provider credentials this machine does not have. That is pre-existing, not introduced here — verified against a pristine origin/main worktree:

origin/main   FAILED | 26 passed (510 steps) | 7 failed (22 steps)
this branch   FAILED | 26 passed (517 steps) | 7 failed (22 steps)

The set of failing test names is byte-for-byte identical between the two (diff of the sorted names is empty). The branch adds 7 passing steps and no new failures.

Second commit: review cleanups

Self-review after the first commit found three things worth fixing rather than defending.

  • waitForAnthropicStreamReplay duplicated waitForProviderStreamRetry, and duplicated it worse: no up-front aborted check, so an already-cancelled request burned the full delay. The existing helper is now exported and reused.
  • The tool-name registry snapshot was unreachable. Every parser mutation (set at anthropic-stream.ts:1165, delete at :1214) is immediately followed by a yield, so any registry change implies yieldedThisAttempt, which blocks the replay. Dead code presented as a guard; removed.
  • The wrong-body hole above, plus two reachable behaviours with no coverage at all: a replay of a pause_turn continuation, and the budget being shared across the call.

Evidence

Staging health lane, claude-opus-4-6 failing both the text and the tool probe:

Run Probe Error
07:07Z tool OVERLOADED_ERROR
07:12Z text OVERLOADED_ERROR
07:19Z tool OVERLOADED_ERROR
08:09Z text OVERLOADED_ERROR

Events on every one: AGENT_RUN_DEFAULT_CHAT_START_ENQUEUED, RUN_STARTED, CUSTOM, STEP_STARTED, RUN_ERROR — no output before the failure, which is exactly the window this replay covers.

The staging log for run_4dfbef51-907c-464c-b5d2-cb2f65e6961b names the path:

ProviderOverloadedError: Anthropic stream failed: provider overloaded
    at buildAnthropicSseError (ext-llm-anthropic/src/anthropic-stream.js:212:16)
    at streamAnthropicCompatibleParts (:696:27)
    at async continuePausedStream (anthropic-provider.js:396:38)
    at async Object.pull (anthropic-provider.js:243:30)

Object.pull is the tell: the failure is raised while reading the body, so it is structurally out of reach of the pre-header loop.

Five such events in staging today between 06:45Z and 07:24Z, four in production over the preceding week. The lane passed at 06:43Z and 06:58Z with runs seconds either side of a failure, so the overload is per-request and transient — the case a bounded replay is for.

Note on the health gate

This is the defect behind the red lane, but landing it does not turn staging green by itself: the fix reaches staging only once a veryfront-code release is promoted into veryfront-server, which currently runs 0.1.36-rc.53.

Related

Same shape as veryfront-issue-inbox#710 (retryable: true built and then ignored), which veryfront-code#3993 closed for the pre-header window. This closes the post-header, pre-output window that #3993 could not reach.

Anthropic answers HTTP 200 and then delivers `overloaded_error` as the
first SSE event. `requestStream` builds that error with `retryable: true`
but cannot act on it: it hands the body off the moment headers arrive,
and past that point `if (streamOwnsDeadline) throw error` surfaces every
failure because a reader already owns the body. The run therefore ends
in RUN_ERROR on a transient upstream 529.

Replay it where the request body is still available. The bound (2) and
the backoff (1s, 2s) match the pre-header loop, so the worst case adds 3
seconds and a hosted child fork stays inside its 45-second idle
watchdog.

Two guards keep the replay safe, both mutation-proven:

- nothing may have been yielded for the current request, so a mid-turn
  failure can never duplicate output
- the failure must be a typed `ProviderError` with `retryable: true`, so
  an authentication error or caller cancellation is never replayed

Observed on the staging health lane: `claude-opus-4-6` failed both the
text and the tool probe with OVERLOADED_ERROR across four runs between
2026-08-24 06:45Z and 08:11Z, from `buildAnthropicSseError` inside
`Object.pull`.
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 8 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c983304-8a3e-45c7-b825-fe6f8435ce3c

📥 Commits

Reviewing files that changed from the base of the PR and between a6fe724 and c7535f8.

📒 Files selected for processing (6)
  • docs/api-reference/veryfront/provider.md
  • extensions/ext-llm-anthropic/src/anthropic-provider.test.ts
  • extensions/ext-llm-anthropic/src/anthropic-provider.ts
  • src/provider/runtime-loader.ts
  • src/provider/runtime-loader/provider-http.ts
  • src/provider/shared/index.ts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

📦 Client bundle boundary

Entrypoint Modules Source size Server leaks
src/index.client.ts 327 1964 KiB ✅ 0

A server module in a client graph aborts hydration in the browser. New leaks fail CI; known leaks are tracked in scripts/lint/client-bundle-baseline.json to burn down.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 49a0e4f98f

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/ext-llm-anthropic/src/anthropic-provider.ts Outdated
Comment thread extensions/ext-llm-anthropic/src/anthropic-provider.ts Outdated
@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.00000% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...nsions/ext-llm-anthropic/src/anthropic-provider.ts 97.75% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds bounded replay of retryable Anthropic SSE failures that occur before output and consolidates stream issuance so initial, replayed, and pause_turn continuation requests share consistent timeout and cancellation handling.

  • Replays pre-output retryable stream errors up to two times with abortable backoff.
  • Resets the replay allowance after consumer-visible output so a distinct continuation can retry independently.
  • Applies shared header-time budgets and a per-replay header cap.
  • Exports the shared retry-delay and timeout primitives and documents the extension-facing API.
  • Adds coverage for overload replay, output/non-retryable guards, continuation bodies and budgets, cancellation, and header deadlines.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
extensions/ext-llm-anthropic/src/anthropic-provider.ts Implements guarded in-stream replay, abortable backoff, independent post-output replay allowances, and shared header budgeting; both previously reported defects are fixed.
extensions/ext-llm-anthropic/src/anthropic-provider.test.ts Adds comprehensive tests for replay eligibility, bounds, continuation request bodies, cancellation, and timing budgets without introducing a review-eligible defect.
src/provider/runtime-loader/provider-http.ts Exports the existing abort-aware retry wait helper and provider stream timeout constants for safe reuse.
src/provider/runtime-loader.ts Re-exports the shared stream retry and timeout primitives through the runtime loader.
src/provider/shared/index.ts Exposes the new shared primitives to provider extensions through the stable shared barrel.
docs/api-reference/veryfront/provider.md Updates generated provider API documentation for the newly exported stream timeout and retry symbols.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Issue Anthropic stream request] --> B{Headers/request succeeded?}
  B -- No --> C[requestStream pre-header retry handling]
  B -- Yes --> D[Parse SSE body]
  D --> E{Retryable provider error?}
  E -- No --> F[Yield parts or surface error]
  E -- Yes --> G{Any output yielded in this attempt?}
  G -- Yes --> H[Surface error without replay]
  G -- No --> I{Replay count and header budget available?}
  I -- No --> H
  I -- Yes --> J[Abortable backoff]
  J --> K[Reissue current request body]
  K --> D
  F --> L{pause_turn continuation?}
  L -- Yes --> M[Build continuation request body]
  M --> A
  L -- No --> N[Emit final result]
Loading

Reviews (10): Last reviewed commit: "Prove replay header caps without wall-cl..." | Re-trigger Greptile

Comment thread extensions/ext-llm-anthropic/src/anthropic-provider.ts
Comment thread extensions/ext-llm-anthropic/src/anthropic-provider.ts Outdated
…ble guard

Review of the replay found three things to clean up.

The local `waitForAnthropicStreamReplay` duplicated
`waitForProviderStreamRetry` in provider-http.ts, and duplicated it
worse: it did not check `aborted` up front, so an already-cancelled
request burned the whole delay before anything noticed. Export the
existing helper instead. The PR claims the replay's backoff matches the
pre-header loop; sharing the wait makes that structural rather than
coincidental.

The tool-name registry snapshot was unreachable. Every mutation in the
parser (`set` at anthropic-stream.ts:1165, `delete` at :1214) is
immediately followed by a yield, so any registry change implies
`yieldedThisAttempt`, which blocks the replay. Restoring it was dead
code presented as a guard.

Replaying the wrong request body was untested: the stub answered
positionally and ignored what was sent, so substituting `body` for
`requestBody` survived. The pause_turn test now asserts the message
count of every request, and that mutation reddens it.

Adds coverage for two reachable behaviours that had none: a replay of a
pause_turn continuation, and the replay budget being shared across the
whole call rather than reset per request.
@github-actions

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: ffc9e252ac

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@kwakayama kwakayama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This was generated by AI during triage.\n\nVerified one blocker: the in-stream replay path resets the shared header/first-output budget on every retry, so repeated overload replays can exceed the intended total admission budget. Carry one deadline across the initial request and all replays, with a regression covering delayed replay headers.

@kwakayama kwakayama added the needs-human-input Maintainer action required label Aug 24, 2026
Exporting `waitForProviderStreamRetry` from `veryfront/provider/shared`
adds it to the public surface, which `ci (lint)` caught as a stale
generated reference. The rest of the diff is line-number churn from the
edits above it.

Making it public is a deliberate trade. `veryfront/provider/shared` is
the toolbox provider implementations build against, and it already
exports `requestStream`, `buildProviderError`, and `parseRetryAfterMs`;
a cancellation-aware retry wait belongs in that set. The alternative was
a second private copy in the extension, which is exactly what let the
missing `aborted` check through the first time.
@github-actions

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: f756f1c807

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Constraint: In-stream Anthropic overload replays must share the same admission budget as the original streamed call.
Rejected: Resetting requestStream budget per in-stream replay | lets repeated provider stalls exceed the hosted idle boundary.
Confidence: high
Scope-risk: narrow
Directive: Keep provider-specific stream replay budgets aligned with shared provider HTTP defaults.
Tested: deno test -A extensions/ext-llm-anthropic/src/anthropic-provider.test.ts; deno fmt --check touched files; git diff --check
Not-tested: Full repository test suite; pre-commit verify:quick blocked by local Deno 2.7.11 vs pinned 2.7.7
@kwakayama kwakayama added ready-for-review Agent-prepared work is ready for human review and removed needs-human-input Maintainer action required labels Aug 24, 2026
@kwakayama
kwakayama enabled auto-merge August 24, 2026 09:19
@github-actions

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8e01871c80

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/ext-llm-anthropic/src/anthropic-provider.ts Outdated
…eplays

Builds on 8e01871, which landed the shared header budget in parallel
with my own version of the same fix. Its anchor is the better idea: it
ties the bound to the 45-second hosted child-fork watchdog directly,
which mine did not. Two defects remained.

The anchor never reset. It is set once at doStream entry and
`performance.now()` keeps advancing while the stream is consumed, so any
turn longer than 40 seconds issues its pause_turn continuation with a
spent budget. That silently strips the continuation of the pre-header
retry veryfront-code#3993 added. Demonstrated before fixing: a
continuation whose first attempt gets a transient 529 was not retried
and the run died. It is re-anchored before each continuation, because
that request opens a new idle window; a replay stays inside the window
it failed in, since a replay only happens when nothing was yielded.

The budget was also unproven. Deleting `totalHeadersBudgetMs` entirely
left every test green, because the existing test exercises the
pre-backoff guard rather than the value handed to `requestStream`. The
cause is that `requestStream` never shortens a request's *first*
attempt to fit the total budget, so a replay still waited the full
30-second per-attempt deadline. Replays now pass both the per-attempt
deadline and the budget, capped at 10 seconds.

Mutation-proven, with the timings naming the mechanism:

  remove the continuation re-anchor       -> RED
  drop the replay clamp entirely          -> RED at 40s (fresh default budget)
  drop only headersTimeoutMs from it      -> RED at 31s (unshortened first attempt)

Also regenerates the API reference. 8e01871 exported
DEFAULT_PROVIDER_STREAM_TOTAL_HEADERS_BUDGET_MS without it, which
`ci (lint)` would have failed on.
@kwakayama
kwakayama disabled auto-merge August 24, 2026 09:33
Constraint: In-stream Anthropic overload replays must not receive a fresh full header wait after the shared first-output budget has already been spent.

Rejected: Passing only totalHeadersBudgetMs to requestStream | each Anthropic replay is a new requestStream first attempt, so that does not shorten its header deadline.

Confidence: high

Scope-risk: narrow

Directive: Preserve caller abort propagation through providerAbortScope when adjusting Anthropic stream replay timing.

Tested: deno test --allow-env --allow-net --allow-read --allow-write extensions/ext-llm-anthropic/src/anthropic-provider.test.ts

Tested: deno test --allow-env --allow-net --allow-read --allow-write --allow-run=deno extensions/ext-llm-anthropic/src/anthropic-provider.test.ts extensions/ext-llm-anthropic/src/anthropic-stream.test.ts extensions/ext-llm-anthropic/src/anthropic-request-builder.test.ts extensions/ext-llm-anthropic/src/anthropic-native-content.test.ts extensions/ext-llm-anthropic/src/anthropic-mcp-request.test.ts extensions/ext-llm-anthropic/src/index.test.ts

Tested: deno fmt --check extensions/ext-llm-anthropic/src/anthropic-provider.ts extensions/ext-llm-anthropic/src/anthropic-provider.test.ts

Tested: deno lint extensions/ext-llm-anthropic/src/anthropic-provider.ts extensions/ext-llm-anthropic/src/anthropic-provider.test.ts

Tested: git diff --check

Not-tested: Full pre-commit verify:quick; blocked locally by Deno 2.7.11 while repository pins 2.7.7.
@kwakayama
kwakayama enabled auto-merge August 24, 2026 09:52
@kwakayama
kwakayama added this pull request to the merge queue Aug 24, 2026
@github-actions

Copy link
Copy Markdown

@codex review

Closes the last open review finding. Greptile flagged that
`streamReplayCount` spans pause_turn continuations, so a request that
spends both replays leaves a later continuation with none and kills a
run the replay exists to save.

I declined this earlier for a reason that no longer holds. The argument
was that per-request counts let repeated stalls grow without bound,
which was true while the count was the only bound. It is not any more:
the header budget anchored per idle window, plus the 10-second replay
clamp, bound the time independently. With time bounded, resetting the
count per window is safe, and leaving it shared is now incoherent with
the header budget, which already resets there.

Both budgets therefore reset at the same place, for the same reason: a
continuation opens a new idle window. Neither resets on the replay path.
That distinction is load-bearing, not stylistic.

Mutation-proven:

  remove the per-window count reset  -> RED (continuation gets no replay)
  also reset on the replay path      -> RED, as an unbounded loop

The test asserting the old shared-count behaviour is replaced rather
than kept, because this commit deliberately reverses that decision.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 509f44a1c4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread docs/api-reference/veryfront/provider.md Outdated
@kwakayama
kwakayama removed this pull request from the merge queue due to a manual request Aug 24, 2026
@kwakayama

kwakayama commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Review findings: all five addressed

Head is 89e02deb25. Every finding from both reviewers, with the commit that closes it and the mutation that proves it.

# From Sev Finding Closed by Proof
1 Codex P1 Share the header budget across stream replays 8e01871c + f4261ed3 drop the clamp -> RED at 40s
2 Codex P2 Reject immediately when the replay signal is already aborted ffc9e252 n/a - duplicate helper deleted
3 Greptile P1 Replay budget spans continuation requests 89e02deb remove the per-window reset -> RED
4 Greptile P2 Backoff misses an existing abort ffc9e252 n/a - duplicate helper deleted
5 Codex P1 Clamp replay header timeouts to the remaining budget f4261ed3 + 509f44a1 drop headersTimeoutMs -> RED at 31s

On #2 and #4

Both are the same defect, and both are gone because the code they describe is gone. The local waitForAnthropicStreamReplay was a worse duplicate of waitForProviderStreamRetry in provider-http.ts, which already does throwIfAborted() up front and if (aborted) onAbort() after registration. The existing helper is now exported and reused, so there is one implementation and one place for this to be right.

On #3, which I declined earlier and now accept

My original reasoning was that per-request replay counts let repeated stalls grow without bound. That was true while the count was the only bound. Finding #1 changed that: the header budget now resets per idle window and replays are clamped to 10 seconds, so time is bounded independently of the count.

With time bounded, a shared count buys nothing and costs a run: a first request that spends both replays leaves a healthy pause_turn continuation with none. Worse, it was incoherent - the header budget already reset at the continuation, and the count did not.

Both budgets now reset at the same place for the same reason: a continuation opens a new idle window. Neither resets on the replay path. That distinction is load-bearing rather than stylistic, and it is pinned:

remove the per-window count reset  -> RED, continuation gets no replay
also reset on the replay path      -> RED, as an unbounded loop

The test that asserted the old shared-count behaviour is replaced, not kept, because this reverses that decision deliberately.

On #5

Worth recording why the first attempt at this did not work. Passing totalHeadersBudgetMs alone left the replay waiting the full 30 seconds, because requestStream never shortens a request's first attempt to fit the total budget - it only governs that request's own internal retries, and every replay is a new first attempt. Codex named this precisely. Replays now pass the per-attempt deadline as well, and 509f44a1 further clamps both to min(ceiling, remaining) so a nearly-spent window cannot hand out a fresh 10 seconds.

The mutation timings name the mechanism rather than just failing:

drop the clamp entirely      -> RED at 40s  (fresh default budget)
drop only headersTimeoutMs   -> RED at 31s  (unshortened first attempt)

One defect found while reconciling, not raised by either reviewer

8e01871c anchored the shared budget once at doStream entry and never re-anchored it. performance.now() keeps advancing while the stream is consumed, so any turn longer than 40 seconds issued its pause_turn continuation with a spent budget - silently stripping the continuation of the pre-header retry veryfront-code#3993 added.

Demonstrated before fixing: a continuation whose first attempt got a transient 529 was not retried and the run died. Covered by still grants a pause_turn continuation its pre-header retry budget after a long turn.

Queue note - correction

This PR was in the merge queue at position 23. I dequeued it to push #3, because a queued branch cannot be updated. It was not merged at any point.

An earlier version of this comment said I had re-queued it. That was wrong. Dequeuing also cleared auto-merge, and I did not restore either. The PR is currently OPEN, out of the queue, with auto-merge DISABLED, and it will not merge until someone re-enables it. Re-arming it is a deliberate choice for a human to make, not something to restore silently - especially since CI on this head is not clean yet.

@github-actions

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 89e02deb25

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

The stub for the header-wait cap returned a promise that never settled.
A real fetch rejects when its signal aborts, so the stub now does too.

This is hygiene, not a fix. I suspected it caused the `coverage shard
6/8` failure on this head, which reported "Promise resolution is still
pending but the event loop has already resolved". It did not: the
anthropic suite runs in shard 2, which passed, and re-running shard 6
was green, so that failure was flaky. Running this file under
`--trace-leaks` with the old stub does not flag it either.

Keeping the change anyway because a permanently pending promise is a
poor stub whether or not a sanitizer catches it today.

Verified the reworked stub still detects the mutation it exists for:
dropping the replay clamp reddens it at 39 seconds, the same as before.
@github-actions

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0530611bc7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/ext-llm-anthropic/src/anthropic-provider.ts Outdated
@kwakayama kwakayama removed the ready-for-review Agent-prepared work is ready for human review label Aug 24, 2026
…tions

Re-anchor the stream header budget (and the replay count) at the moment
a part is yielded to the consumer, because that is when the hosted
watchdog's idle window restarts. A pause_turn continuation no longer
gets a fresh budget: it draws on what remains of the window opened by
the last yielded part, fails fast with a retryable error when that
window is already spent, and clamps its per-attempt header deadline to
the remaining window so a request issued late in a window cannot wait
for headers beyond it.

Export DEFAULT_PROVIDER_STREAM_HEADERS_TIMEOUT_MS through the provider
barrels so the Anthropic runtime can apply the clamp.

Trim the exported JSDoc on requestStream and the total header budget so
the generated public API reference no longer names hosted watchdog
internals; the rationale stays in the source as a non-doc comment, and
the API reference is regenerated to match.

Claude-Session: https://claude.ai/code/session_016wezkD1Xnjjk5gQiCDHGGZ
@github-actions

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f15b24f9f4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/ext-llm-anthropic/src/anthropic-provider.test.ts Outdated
@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 24, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 24, 2026
The replay timeout test used elapsed wall time to prove the 10s header cap, which made the focused Anthropic suite spend roughly eleven seconds in one assertion. Drive the retry delay and replay header timeout with FakeTime instead, and assert the replay signal remains active until the exact fake deadline before it aborts.

Also remove review/internal wording from the touched Anthropic source comments and make the replay comment distinguish 3s of backoff from the shared header/idle-window budget and per-replay 10s header cap.

Constraint: Preserve the 10s production replay header budget and existing replay behavior

Rejected: Add timeout injection to production code | test-only determinism is enough and avoids widening the public surface

Confidence: high

Scope-risk: narrow

Tested: deno task test:file extensions/ext-llm-anthropic/src/anthropic-provider.test.ts

Tested: deno fmt --check extensions/ext-llm-anthropic/src/anthropic-provider.ts extensions/ext-llm-anthropic/src/anthropic-provider.test.ts

Tested: deno check --frozen extensions/ext-llm-anthropic/src/anthropic-provider.ts extensions/ext-llm-anthropic/src/anthropic-provider.test.ts

Tested: git diff --check
@github-actions

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: c7535f8d72

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 24, 2026
Merged via the queue into main with commit da33cbf Aug 24, 2026
49 checks passed
@kojiwakayama
kojiwakayama deleted the fix/anthropic-sse-overloaded-retry branch August 24, 2026 20:51
@kwakayama kwakayama added the needs-human-input Maintainer action required label Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human-input Maintainer action required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants