Repository navigation
Description
Summary
V2 classifies provider HTTP 401/403 responses as authentication errors but does not connect those failures back to the credential or integration that authorized the request. GitHub Copilot and potentially other OAuth integrations need a consistent way to invalidate or refresh credentials, update derived endpoint metadata, and safely retry after an authentication rejection.
Environment
- opencode version: v0.0.0-next-15919
- OS: Darwin 25.5.0, arm64
- Terminal: Ghostty, TERM=xterm-256color, COLORTERM=truecolor
- Shell: /bin/zsh
- Install/channel: next, local V2 development
- Active plugins: caffeinate.ts, plugin.ts
Reproduction
- Authenticate a V2 OAuth integration such as GitHub Copilot.
- Allow the stored credential, authorization state, or account-specific endpoint metadata to become stale or invalid.
- Make a provider request that returns HTTP 401 or an integration-recoverable HTTP 403.
- Observe that the request fails without invalidating or refreshing the credential, rediscovering integration metadata, or retrying the request.
Expected Behavior
V2 should have a consistent status-aware credential recovery path that retains the identity of the integration connection used for each provider request.
- A 401 should invalidate or refresh the associated credential when the integration supports recovery.
- A 403 should invoke integration-specific policy because it may represent either recoverable stale authorization state or a permanent permission denial.
- Successful refreshes and metadata changes should be persisted to the same credential.
- GitHub Copilot recovery should also rediscover account-specific API endpoint metadata when appropriate.
- The failed provider request should be retried at most once after successful recovery.
- Concurrent failures for one credential should share a single recovery operation.
- Unsupported or failed recovery should surface a clear reauthentication requirement without retry loops.
- Existing OAuth integrations should be audited for the same behavior.
Actual Behavior
V2 currently only classifies these responses:
- HTTP 401 becomes
Authentication(kind: "invalid"). - HTTP 403 becomes
Authentication(kind: "insufficient-permissions"). - Authentication failures are explicitly non-retryable.
Integration.connection.resolve refreshes only from stored expiration and only when the integration defines a refresh implementation. Provider status failures are not connected back to credential invalidation or refresh. GitHub Copilot's request wrapper does not inspect 401/403 responses and its OAuth method does not define refresh behavior.
Additional Context
Relevant V2 areas:
packages/ai/src/provider-error.tsclassifies 401/403 responses.packages/core/src/session/runner/retry.tsmarks authentication failures as non-retryable.packages/core/src/integration.tsrefreshes expiring credentials during connection resolution.packages/core/src/plugin/provider/github-copilot.tssupplies the stored OAuth credential to provider requests without status-aware recovery.
This should be designed as integration-aware recovery rather than a global unconditional retry for every 403 response.
Activity
adbcodes commentedon Aug 25, 2026
heyy, I have a potential implementation ready with tests passing. However, I couldn’t test the full GitHub Copilot flow because Copilot does not appear to be fully connected to the V2 model picker/session runner yet.
Is this recovery work expected to be implemented now, or after the remaining V2 Copilot integration?
if it’s ready for contributions, may I take this issue and prepare a PR?
opencodejr commentedon Oct 28, 2026
Thanks for the detailed write-up. I checked the current
devcode (77a0fd6) and it matches the behaviour you describe:packages/ai/src/provider-error.tsturns 401/403 intoAuthenticationError.packages/core/src/session/runner/retry.tsdoes not retry authentication errors.Integration.connection.resolveonly calls an integration'srefreshwhen the stored credential is about to expire.expires: 0and has norefresh. It looks upapiEndpointonly at login, and its request wrapper ignores 401/403 responses.Nothing here is broken against what the code currently sets out to do. There is simply no status-aware recovery path yet. So I'm recording this as working as designed and better tracked as a feature/design request than as a reproduced bug. The proposed design (keeping the connection identity on each request, per-integration 401/403 policy, one shared recovery per credential, at most one retry, a clear reauthentication error) looks like a good scope for that work.