Skip to content

v2: recover integration credentials after 401/403 responses #38203

Open

Description

@rekram1-node

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

  1. Authenticate a V2 OAuth integration such as GitHub Copilot.
  2. Allow the stored credential, authorization state, or account-specific endpoint metadata to become stale or invalid.
  3. Make a provider request that returns HTTP 401 or an integration-recoverable HTTP 403.
  4. 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.ts classifies 401/403 responses.
  • packages/core/src/session/runner/retry.ts marks authentication failures as non-retryable.
  • packages/core/src/integration.ts refreshes expiring credentials during connection resolution.
  • packages/core/src/plugin/provider/github-copilot.ts supplies 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

  1. adbcodes commented on Aug 25, 2026

    @adbcodes

    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?

  2. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the detailed write-up. I checked the current dev code (77a0fd6) and it matches the behaviour you describe:

    • packages/ai/src/provider-error.ts turns 401/403 into AuthenticationError.
    • packages/core/src/session/runner/retry.ts does not retry authentication errors.
    • Integration.connection.resolve only calls an integration's refresh when the stored credential is about to expire.
    • The GitHub Copilot OAuth method stores the token with expires: 0 and has no refresh. It looks up apiEndpoint only 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions