Skip to content

feat(opencode): add serializable providerFailure classification vocabulary - #1108

Merged
Astro-Han merged 2 commits into
devfrom
claude/i1105-provider-failure-vocab
Jun 3, 2026
Merged

feat(opencode): add serializable providerFailure classification vocabulary#1108
Astro-Han merged 2 commits into
devfrom
claude/i1105-provider-failure-vocab

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Jun 3, 2026

Copy link
Copy Markdown
Owner

What

Slice ② of #1105. Introduce one canonical, serializable provider-failure discriminant (providerFailure) so retry, UI, and observability can read a single field instead of re-sniffing error strings.

  • Add ProviderFailureKind zod enum: auth, rate_limit, quota_exhausted, server_overload, invalid_request, transport_disconnect, decompression, unknown.
    • free_quota_exhausted is intentionally excluded — it is a retry-time concept that depends on retry-after headers and wall-clock resetAt, not a parse-time property. Context overflow keeps its own ContextOverflowError name.
  • Classify the kind once at parse time:
    • parseStreamError carries kind + code for the codes it already handles (insufficient_quota/usage_not_includedquota_exhausted, invalid_promptinvalid_request, server_is_overloaded/server_errorserver_overload). Unknown codes still return undefined (unchanged).
    • parseAPICallError derives the kind from status code + body error code via apiCallErrorKind.
  • Carry providerFailure { kind, code } on APIError.data and populate it in the transport_disconnect, decompression, APICallError, and stream-error branches of fromError.
  • The schema field is optional for back-compat with rows persisted before it existed; consumers fall back to message sniffing when it is absent.

Why

#1105 tracks unifying provider-failure classification behind one serializable discriminant. Today every consumer (retry, UI, observability) re-sniffs message/statusCode/code independently, which drifts. This slice establishes and populates the vocabulary so later slices can consolidate the decision logic onto a single field.

Scope boundary

No consumer reads providerFailure yet. classifyRetry and other decision functions keep their existing behavior verbatim (the retry-notice copy is user-facing and stays unchanged). Consolidating consumers onto the new field lands in a later slice. This PR is purely additive vocabulary + population + persistence schema.

Verification

  • bun test test/session/message-v2.test.ts — 63 pass (6 new: transport/decompression/status-code population, schema round-trip + back-compat + unknown-kind rejection).
  • bun test test/session/retry.test.ts — 35 pass (unchanged behavior confirmed).
  • bun run typecheck (tsgo --noEmit) — clean.

Part of #1105.

Summary by CodeRabbit

  • Improvements

    • Enhanced error classification system for API failures, enabling clearer identification of quota limits, invalid requests, and server issues
    • Improved error metadata with detailed failure information
  • Tests

    • Expanded test coverage for error scenarios, classification logic, and backward compatibility

…ulary

Introduce one canonical, serializable provider-failure discriminant so retry,
UI, and observability can read a single field instead of re-sniffing error
strings.

- Add ProviderFailureKind zod enum (auth, rate_limit, quota_exhausted,
  server_overload, invalid_request, transport_disconnect, decompression,
  unknown). free_quota_exhausted stays a retry-time concept and is
  intentionally excluded; context overflow keeps ContextOverflowError.
- Classify the kind once at parse time: parseStreamError carries kind+code for
  the codes it already handles; parseAPICallError derives kind from status code
  and body error code via apiCallErrorKind.
- Carry providerFailure { kind, code } on APIError.data and populate it in the
  transport_disconnect, decompression, APICallError, and stream-error branches
  of fromError.
- Schema field is optional for back-compat with rows persisted before it
  existed; consumers fall back to message sniffing when absent.

No consumer reads providerFailure yet — classifyRetry and other decision
functions keep their existing behavior; that consolidation lands in a later
slice. This slice only establishes and populates the vocabulary.

Part of #1105.

Tests: bun test test/session/message-v2.test.ts test/session/retry.test.ts;
bun run typecheck.
@Astro-Han Astro-Han added the enhancement New feature or request label Jun 3, 2026
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Astro-Han, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 13 minutes and 52 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ca31f94f-b2c8-4372-8928-db509002925c

📥 Commits

Reviewing files that changed from the base of the PR and between 01860ea and 87aa629.

📒 Files selected for processing (2)
  • packages/opencode/src/provider/error.ts
  • packages/opencode/test/session/message-v2.test.ts
📝 Walkthrough

Walkthrough

This PR introduces a unified provider failure classification system that maps diverse error signals—HTTP status codes, provider-specific error codes, transport disconnects, and decompression failures—to a canonical ProviderFailureKind enum. The classification is threaded through error parsing (ParsedStreamError, ParsedAPICallError) and serialized in the public APIError metadata for observability and client error handling.

Changes

Provider Failure Classification

Layer / File(s) Summary
Classification contract and kind mapping
packages/opencode/src/provider/error.ts
ProviderFailureKind enum defines canonical failure categories: auth, rate_limit, server_overload, quota_exhausted, invalid_request, transport_disconnect, decompression. Helper apiCallErrorKind(statusCode, code) derives kind from HTTP status and provider error code.
Stream error parsing integration
packages/opencode/src/provider/error.ts
ParsedStreamError.api_error type extended with optional kind and code fields. parseStreamError extracts provider error code and populates kind for quota failures (insufficient_quota/usage_not_included), invalid requests (invalid_prompt), and server overload.
API call error parsing integration
packages/opencode/src/provider/error.ts
ParsedAPICallError.api_error type extended with optional kind and code fields. parseAPICallError extracts code and computes kind via apiCallErrorKind(), returning both in the error object.
APIError schema and fromError integration
packages/opencode/src/session/message-v2.ts
APIError Zod schema adds optional metadata.providerFailure: { kind: ProviderFailureKind; code?: string }. fromError populates providerFailure across transport disconnects, decompression failures (gzip/br), APICallError instances, and parsed stream errors.
Test coverage and backward compatibility
packages/opencode/test/session/message-v2.test.ts
Updated error-code classification tests assert providerFailure. New tests cover transport disconnect, decompression, and parameterized APICallError status-to-kind mapping. Back-compat suite validates legacy persisted rows parse with undefined providerFailure, round-trip serialization, and rejection of unknown kinds.

Sequence Diagram

sequenceDiagram
  participant Transport
  participant Decompression
  participant APICallError
  participant StreamParse
  participant FromError
  participant Metadata["APIError.metadata"]
  
  Transport->>FromError: transport.code
  FromError->>Metadata: providerFailure.kind = transport_disconnect
  
  Decompression->>FromError: FetchDecompressionError.code
  FromError->>Metadata: providerFailure.kind = decompression
  
  APICallError->>FromError: parsed.kind + parsed.code
  FromError->>Metadata: providerFailure = {kind, code}
  
  StreamParse->>FromError: parsed.kind + parsed.code
  FromError->>Metadata: providerFailure = {kind, code}
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐰 A failure, once hidden, now wears its true name,
From transport to quota, each error's the same—
One kind to classify them, one schema to hold,
The provider's confession, so perfectly told! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: introducing a serializable providerFailure classification vocabulary.
Description check ✅ Passed The PR description covers all critical sections: What, Why, scope boundary, verification steps with test results, and addresses the template requirements with comprehensive context.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/i1105-provider-failure-vocab

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 and usage tips.

@github-actions github-actions Bot added harness Model harness, prompts, tool descriptions, and session mechanics P2 Medium priority labels Jun 3, 2026

@github-actions github-actions 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.

Suggested priority: P2 (includes non-doc, non-test paths outside the low-risk bucket).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@Astro-Han Astro-Han added the tech-debt Supplemental cleanup, maintainability, architecture, test, or quality debt context label Jun 3, 2026

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a canonical, serializable classification for provider and API failures (ProviderFailureKind) using Zod, updating error parsing utilities and the APIError schema to carry this classification for improved observability and retry handling. Feedback suggests mapping HTTP status codes 400 and 422 to the invalid_request failure kind to improve classification accuracy, along with adding corresponding test cases to verify this behavior.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/opencode/src/provider/error.ts
Comment thread packages/opencode/test/session/message-v2.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/opencode/test/session/message-v2.test.ts (1)

1700-1724: ⚡ Quick win

Consider adding test coverage for server_is_overloaded code.

The PR objectives mention "server_is_overloaded / server_error → server_overload", but only server_error is explicitly tested. Adding a test case for the server_is_overloaded code would provide explicit coverage of this mapping, which is part of the stated PR scope.

You could add it to the parameterized test at lines 1659-1698 or create a similar dedicated test case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/test/session/message-v2.test.ts` around lines 1700 - 1724,
Add a test that mirrors the existing "serializes OpenAI response server_error
stream chunks as retryable APIError" case but uses an error body with error.code
=== "server_is_overloaded" (and type "error"/"server_error" as appropriate) and
assert that MessageV2.fromError(...) produces providerFailure: { kind:
"server_overload", code: "server_is_overloaded" } (and the same
isRetryable/responseBody/providerID/message expectations); place it alongside
the existing test (or add to the parameterized block) and use the same callsite,
MessageV2.fromError, and expectation shape so the mapping from
"server_is_overloaded" → "server_overload" is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/opencode/test/session/message-v2.test.ts`:
- Around line 1700-1724: Add a test that mirrors the existing "serializes OpenAI
response server_error stream chunks as retryable APIError" case but uses an
error body with error.code === "server_is_overloaded" (and type
"error"/"server_error" as appropriate) and assert that MessageV2.fromError(...)
produces providerFailure: { kind: "server_overload", code:
"server_is_overloaded" } (and the same
isRetryable/responseBody/providerID/message expectations); place it alongside
the existing test (or add to the parameterized block) and use the same callsite,
MessageV2.fromError, and expectation shape so the mapping from
"server_is_overloaded" → "server_overload" is covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1cbca373-36b9-42b2-90c5-41b3665c4876

📥 Commits

Reviewing files that changed from the base of the PR and between f6a1328 and 01860ea.

📒 Files selected for processing (3)
  • packages/opencode/src/provider/error.ts
  • packages/opencode/src/session/message-v2.ts
  • packages/opencode/test/session/message-v2.test.ts

400 (Bad Request) and 422 (Unprocessable Entity) are client-side request
rejections. Overflow 4xx is already routed to context_overflow before
apiCallErrorKind runs, so what reaches here is a genuine invalid request rather
than an over-long prompt. Map them to invalid_request instead of unknown to
improve classification coverage for errors that carry no specific error code.

Addresses review feedback on #1108.
@Astro-Han
Astro-Han merged commit c5cd3a7 into dev Jun 3, 2026
33 checks passed
Astro-Han added a commit that referenced this pull request Jun 3, 2026
Slice ④ of #1105. Make the retry consumer read the canonical providerFailure.kind
(landed in slice ② via #1108) instead of re-deriving the retry/stop decision from
the provider SDK's isRetryable flag.

What
- classifyRetry's APIError gate keys off providerFailure.kind: terminal kinds
  (auth, invalid_request, quota_exhausted) never retry; transient kinds
  (rate_limit, server_overload, transport_disconnect, decompression) always do.
- `unknown` kinds and rows that predate providerFailure fall back to the legacy
  isRetryable + 5xx signal, which agrees with the kind classification for every
  classified case today, so behavior is unchanged for real inputs.
- Reading the kind makes the decision robust against a mis-set isRetryable flag.

Why
- #1105 unifies provider-failure classification behind one serializable
  discriminant read by every consumer. Slice ② populated providerFailure; this
  slice makes the retry path consume it, collapsing the retry-time string-sniffing
  classification onto the parse-time one and removing the drift risk between them.

Scope boundary (option A, chosen with the maintainer)
- Retry-notice copy is unchanged: the provider's descriptive message is still
  shown during retries. Standardized per-kind copy and actionable affordances
  belong to the design-gated UI slice ⑥ where the UI reads kind.
- free_quota_exhausted stays a retry-time concept and is still detected from the
  opencode FreeUsageLimitError marker; non-APIError plain-text fallbacks are kept
  for errors that carry no providerFailure.

Verification
- bun test src/session/retry.test.ts (23 pass, 7 new).
- bun test test/session/retry.test.ts test/session/message-v2.test.ts
  test/session/retry-decision.test.ts test/session/processor-rate-limit.test.ts
  (all pass, behavior unchanged).
- bun run typecheck (tsgo --noEmit) clean.
- codex review: no blocking findings.

Part of #1105.
@Astro-Han
Astro-Han deleted the claude/i1105-provider-failure-vocab branch June 3, 2026 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request harness Model harness, prompts, tool descriptions, and session mechanics P2 Medium priority tech-debt Supplemental cleanup, maintainability, architecture, test, or quality debt context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant