Repository navigation
Design extensible Session error details #35325
Copy link
Copy link
Open
Labels
2.0coreAnything pertaining to core functionality of the application (opencode server stuff)Anything pertaining to core functionality of the application (opencode server stuff)discussionUsed for feature requests, proposals, ideas, etc. Open discussionUsed for feature requests, proposals, ideas, etc. Open discussiongang-grillDesign topics queued for later group grillingDesign topics queued for later group grilling
Description
Context
#35272 intentionally keeps the public Session error wire contract minimal and forward-compatible:
type SessionError = {
type: string
message: string
}Core may classify failures more precisely internally, but internal provider, permission, and tool fields should not become permanent public commitments without demonstrated consumers and an explicit compatibility model.
Goal
Design the next public Session error model, if richer machine-readable details are justified, without requiring database resets or breaking older SDK clients when new error classifications or fields are introduced.
Investigation
- Inventory concrete consumer needs across the TUI, App, SDK users, automation, telemetry, retries, authentication recovery, permission display, and tool diagnostics.
- Audit established error contracts and document the tradeoffs behind them:
- RFC 9457 Problem Details
- JSON:API error objects
- Stripe and GitHub REST errors
- GraphQL errors and extensions
- gRPC Status and typed details
- OpenAI and other relevant model-provider APIs
- Compare at least these candidate shapes with ideal consumer call sites:
- Minimal open
{ type, message } - Open envelope plus optional JSON
detailsordata - Stable coarse category plus open type
- Known typed details layered over an open wire envelope
- Versioned rich error contracts
- Minimal open
- Pressure-test unknown future types, malformed known details, persisted historical events, mixed-version clients, generated SDK ergonomics, and whether a generic field bag becomes an ungoverned junk drawer.
- Separate internal failure algebras from the public transport contract and identify which information belongs in logs, events, dedicated fields, or error details.
Compatibility Laws
- Older clients must accept newly introduced error type values.
- Unknown types must remain renderable through
message. - New optional fields must not make historical durable events undecodable.
- No required per-type field may be added without an explicit versioning and migration decision.
- Retry policy must not depend on public error details unless that behavior is deliberately part of the contract.
- Public type values must not be renamed or repurposed after release.
Deliverables
- A survey table of analogous APIs and the constraints each design addresses.
- An inventory of actual OpenCode consumer requirements.
- Two or three candidate wire contracts with generated TypeScript examples and fallback handling.
- A recommended contract, naming rationale (
type,code,name,details, ordata), and compatibility laws. - A persistence/versioning decision and migration analysis.
- Focused schema and mixed-version compatibility tests for the selected design.
Non-goals
- Do not expose the current internal error union merely because it already exists.
- Do not add a generic details bag until at least one concrete consumer and governance rule justify it.
- Do not change retry semantics as part of the error-shape design.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
2.0coreAnything pertaining to core functionality of the application (opencode server stuff)Anything pertaining to core functionality of the application (opencode server stuff)discussionUsed for feature requests, proposals, ideas, etc. Open discussionUsed for feature requests, proposals, ideas, etc. Open discussiongang-grillDesign topics queued for later group grillingDesign topics queued for later group grilling
Activity
Dante-dan commentedon Oct 1, 2026
I surveyed the current consumers at dev
0112a92. My recommendation is to retain the two-field envelope first, make its compatibility boundary explicit, and defer additional fields until a migrated consumer requires them. There is one concrete candidate for richer context—ACP auth recovery—but the current consumer uses the legacy contract.Actual consumers and boundaries
UnknownError; recognizes cancellation usingtypesubstringsabort/interrupt.messagealready supplies display; classification should use documented stable exact values rather than substring matching once types exist. No new details needed for this path.retryableto every Session error is unnecessary here.session.error: special-casesMessageAbortedErrorand the textSSE read timed out; otherwise generic notification.data.message/nameand emits the error object as JSON.ProviderAuthError.data.providerIDproducesAuthRequiredError.type: "unknown"; legacy event errors remain aname/dataunion.An important difference from #35272's intended open contract: at this revision,
SessionMessage.UnknownErrorusesSchema.Literal("unknown"), andSessionErrorUnknownalso has the literal type. The generated client repeats that constraint. These readers cannot be assumed forward-compatible with a new type merely because the payload still has two fields. This is static source evidence, not a deployed mixed-version test result.External designs and candidate shapes
{type,message}{type,title,status,detail,instance,...extensions}{code,title,detail,source,meta,...}Three bounded options:
A avoids an extra machine code competing with
type. B makes provider identity a public lifetime commitment but is simpler than C if that is the only proven need. C is justified only by multiple independently evolving consumers; one auth field does not justify an extensibility framework. For future stable names, use semantic lower-case values, not internal class names or provider SDK names; do not emit new values until the reader transition is decided. Existingunknownmust retain its meaning.Compatibility and persistence decision
typevalues. Existing literal-only generated clients/schema decoders are a real constraint: an unversioned server cannot promise compatibility by just changing its latest SDK. Until old-reader behavior is verified or a capability/version boundary exists, keep emittingunknownto those readers. Do not pretend a string catch-all added today repairs yesterday's client.messagerequired and usable for every valid error. Unknown types render it; known types with missing optional context render it too. Never use substring classification or parse message text to drive auth/retry decisions.unknownevents remain valid forever.After design approval, focused checks should cover: historical
unknownreplay; future unknown type + message; extra optional fields; missing/wrong-shaped optional context; malformed required fields; generated SDK compile/schema decode; old reader/new writer and new reader/old writer; and no change to retry/auth fallback behavior. The literal-only old-reader case should be included as an expected incompatibility until the transition resolves it, rather than hidden by a new-SDK-only test.Could the core team review A first, with the reader/writer transition explicitly included, and keep B conditional on ACP migration needing provider identity? This survey is static analysis; no wire/schema implementation or runtime tests have been performed.
fix(opencode): bridge web-child WebSocket upgrades via ws noServer ha…