Repository navigation
refactor: share provider-failure classification between the Action and the gateway - #1869
Conversation
…docs at the shared classifier
fro-bot
left a comment
There was a problem hiding this comment.
Verdict: CONDITIONAL
Blocking issues
| Severity | Location | Issue and required fix |
|---|---|---|
| P2 | provider-failure.ts:142–143 | Hint labels revoke existing retries. labelHint preserves retryability only for explicit isRetryable: true, bypassing the generic ladder’s retryable HTTP 429 and fetch-error cases. For example, {name: 'APIError', data: {statusCode: 429, message: 'no auth available'}} and the string 'fetch failed: no auth available' now return retryable: false despite stop: false. The Action’s attempt-outcome.ts:126 consequently selects a terminal outcome instead of retrying. Derive hint retryability from the generic classification, retaining the hint label and stop: false. |
Non-blocking concerns
None
Missing tests
- Add auth/model hint cases combined with HTTP 429 and an absent/false
isRetryable, plus plain-string fetch failures. Assert preservedretryable: trueand Action retry admission through the SSE/poll failure paths. Current hint tests cover explicit retryability but miss these implicit retry sources.
Risk assessment (MED): likelihood of regression, security exposure, or blast radius
The regression prevents recovery from transient provider failures in the Action. The operator-contract change also requires the documented paired dashboard deployment.
Run Summary
| Field | Value |
|---|---|
| Event | pull_request |
| Repository | fro-bot/agent |
| Run ID | 39209648659 |
| Cache | hit |
| Session | ses_ead2871c1ffekFNuUZOJVNllhe |
| Verification | Source/test inspection and one focused probe; no build, lint, type-check, or test-suite commands |
| Coverage | Seven reviewer returns structurally admitted; adversarial return rejected for schema errors. Its surface was independently covered by correctness/reliability review and the probe. Aggregation did not complete. |
| Delivery | Response-file review; this protocol does not support inline annotations, so the finding links to the exact source lines. |
The following probe tested whether adding hint wording removes otherwise established retryability. Both the 429 and fetch-error controls returned retryable: true; adding auth wording returned retryable: false. All four returned stop: false.
bun -e 'import {classifyProviderFailure, terminalSignalFromPayload} from "./packages/runtime/src/agent/error-format/provider-failure.ts"; for (const payload of [{name:"APIError",data:{statusCode:429,message:"temporary outage"}},{name:"APIError",data:{statusCode:429,message:"no auth available"}},"fetch failed", "fetch failed: no auth available"]) {const result=classifyProviderFailure(terminalSignalFromPayload(payload,{detail:typeof payload==="string"?payload:"Session error: APIError (status 429)"})); console.log(JSON.stringify({payload,kind:result.kind,stop:result.stop,retryable:result.errorInfo.retryable}));}'
fro-bot
left a comment
There was a problem hiding this comment.
Verdict: PASS
The previous blocker is resolved in a9c5336: hint labels now preserve generic retryability. Added tests cover HTTP 429 with absent/false retry flags, fetch-error hints, and Action retry admission through SSE and polling.
Blocking issues
None
Non-blocking concerns
None
Missing tests
None
Risk assessment (LOW): likelihood of regression, security exposure, or blast radius
Low incremental risk from this focused fix. The documented paired gateway/dashboard deployment requirement for contract 1.13.0 remains applicable.
Run Summary
| Field | Value |
|---|---|
| Event | pull_request |
| Repository | fro-bot/agent |
| Run ID | 39213165426 |
| Cache | hit |
| Session | ses_ead2871c1ffekFNuUZOJVNllhe |
| Scope | Follow-up commit a9c5336 against previously reviewed 0e28e76; prior blocker and regression coverage |
| Review | Correctness, testing, project standards, reliability, prior feedback, and TypeScript safety; all six returns admitted with no findings |
| Verification | Diff/source/test inspection only; no build, lint, type-check, test suite, or empirical probe run |
Until now the Action and the gateway each had their own copy of provider-failure classification, and the copies disagreed. A quota retry stopped an Action run, while the gateway waited out OpenCode's whole retry budget. A model retry did the reverse. The two surfaces also read error payloads differently, and the Action's SSE and poll paths labeled the same error differently. Fixes kept landing on one side only (#1283 on the Action, #1854 on the gateway).
This is the first step in consolidating the Action and gateway (see
docs/brainstorms/2026-10-18-action-gateway-consolidation-requirements.md). One runtime function,classifyProviderFailure, now reads a provider signal and returns the failure kind, the strength of the evidence, whether the run stops now, and the label. Both surfaces call it and act on its stop decision, and their own copies are gone.The rule
Model not found:.auth_unavailable,account_rate_limit,model_not_foundandmodel_not_supported. Any other retry keeps waiting.terminalSignalFromPayload, reads OpenCode error payloads for both surfaces. A parity test runs the same fixtures through both and requires them to agree.Behavior changes
account_rate_limitretryprovider-quota-exceededsession.errorwith 402,insufficient_quota, or usage-limit textunclassifiedprovider-quota-exceededinsufficient_quota, or usage-limit textprovider-rejected-requestorunclassifiedprovider-quota-exceededmodel_not_found/model_not_supportedretrydatafields disagreedata.*first (nametop level first), skipping wrong-typed valuesOperator contract 1.13.0
provider-quota-exceeded. This is a MINOR bump.Also
@actions/*imports out ofpackages/runtime.Verification
check-types,lint,test:scripts(688), the full test run, andbuildall pass. Test counts: runtime 1104, action 2594, workspace-agent 773, harness 439, gateway 5330, evals 173.dist/is rebuilt.