Repository navigation
fix(runner): retry only transport failures, and only in one layer - #75
Merged
Merged
Conversation
The runner retried every error an adapter raised except a refusal, up to three attempts: a WireContractError (whose docstring says no retry fixes it), a CheckpointMismatchError, the missing-extra error, a ValueError, and a 401. Both SDKs also retried underneath (TypeSafe's default policy, Anthropic's max_retries=2), so one case could make nine billed calls, and a retried answer could be scored. is_transient() now decides, by the error's shape so the runner imports no SDK: anything plumbline raises on purpose is permanent; a status code is transient only for 408, 409, 425, 429 and 5xx; TimeoutError, ConnectionError, and classes named *ConnectionError or *TimeoutError (Anthropic's do not subclass the builtins) are transient; anything else is permanent, since a recorded failure is visible and a retry costs money. A Retry-After header is waited for, up to 60 seconds. A permanent failure is recorded after its first attempt, with the attempts it made. Both adapters build their SDK client with retries off, so the runner is the only layer and the artifact's attempts is the call count. METHODOLOGY says what is and is not retried. Fixes #26. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
Fixes #26.
The problem. The runner retried every error an adapter raised, except a refusal, up to three attempts. That included:
WireContractError, whose own docstring says no retry fixes it;CheckpointMismatchError;ValueError;Both SDKs also retried underneath (TypeSafe's default policy, Anthropic's
max_retries=2), so one case could make nine billed calls, and a retried answer could be scored.What changed
is_transient()inrunner/execute.pydecides whether an error is worth another attempt. It goes by the error's shape, so the runner imports no SDK:PlumblineError) is permanent;TimeoutError,ConnectionError, and classes named*ConnectionErroror*TimeoutErrorare transient. Anthropic's connection errors subclass neither builtin, hence the name check;Retry-Afteris waited for, up to 60 s.RetryPolicy(max_retries=0)for TypeSafe,max_retries=0for Anthropic). The runner is now the only retrying layer, so the artifact'sattemptsis the number of calls made.Checked
mainand pass now:WireContractError,CheckpointMismatchError, a plainPlumblineError,ValueError, 401 and 400;ConnectionError, and anAPIConnectionError-named class are retried;Retry-After: 2waits 2 s, and600is capped at 60;WireContractErrorand a 401.ruff,mypy --strict, and the fullpytestsuite pass.🤖 Generated with Claude Code