refactor(opencode): name the retry cap for its cumulative scope - #141
Merged
Conversation
maxAttempts sits on the per-reason Retryable descriptor but is compared against Schedule's per-request attempt counter, which every retry reason shares. The name reads as a per-reason budget, so rename it to maxTotalAttempts and document the sharing at both sites. No behavior change. Adds the mixed-reason test the suite was missing: two rate-limit retries leave the output-limit path no resample.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #137. Naming and documentation only — no behavior change.
The mismatch
maxAttemptslives on the per-error descriptor returned byretryable(), so it reads as "attempts allowed for this error". It is actually compared againstSchedule.InputMetadata.attempt, whichmetadataFnimplements as a single++nper schedule instance (effect/dist/Schedule.js:109-127) — one counter per request, incremented on every step regardless of which reason produced it.So the field is a total, not a per-reason budget. Renamed to
maxTotalAttemptsand documented at both sites.Why it matters
processor.ts:733 has one
Effect.retrywrapping the whole stream drain, so two reasons share that counter:llm.stream— no cap, retries indefinitely with backofffinishReason === "length"thrown at processor.ts:524 —maxTotalAttempts: 3429, 429, finishReason=lengthreaches the output-limit branch atattempt=3, so3 >= 3gives it zero resamples and the turn fails with "Model hit its output limit". The sharing is one-directional: rate limits set no cap, so they never lose budget to a prior output-limit attempt.The homogeneous case — the one #137 was designed around — is unaffected and stays at 2 retries / 3 calls, byte-identical to the counter it replaced (
++outputLengthRetries > 2also retried errors 1 and 2 and gave up on 3).Which behavior is right
I did not change it. Bounding total work on a request that has already failed three times is defensible, and the consequence when it fires is one lost resample on a request where the provider was already misbehaving — no data loss, and it is cheaper rather than more expensive. The problem was that the name claimed per-reason scope while the code did cumulative, and no test pinned either reading.
If independent per-reason budgets are actually wanted, that needs a
Map<reason, count>closure inpolicy()— which is the mutable state #137 deliberately deleted, so it should be a conscious choice rather than a silent restoration. Happy to do that instead if you prefer.Tests
Adds the mixed-reason case the suite was missing, so the cumulative semantics are now pinned either way. It fails against a per-reason implementation, which is the point.
bun test test/session/retry.test.ts→ 35 pass.bun typecheckclean.Unrelated, noticed while verifying:
test/session/processor-effect.test.tsis 1 pass / 15 fail onorigin/mainwithModel not found: test/test-model, identical before and after this branch. The only CI workflow istypecheck, so nothing runs the suite on main. Flagging separately, not touching it here.Summary by cubic
Renamed the retry cap to
maxTotalAttemptsto match the shared, cumulative attempt counter across a request. Added inline docs and a mixed‑reason test; no behavior change.Refactors
maxAttemptstomaxTotalAttemptsinretryable()andpolicy().meta.attempt.Tests
Written for commit 76a7a6c. Summary will update on new commits.