Skip to content

V2 refresh deduplication ends before host credential persistence #90

Closed

Description

@jumski

Summary

V2 refresh deduplication ends before the OpenCode host persists the refreshed credential. A second concurrent connection resolve can therefore reuse the still-stored old refresh token and send it to the broker again.

In src/server/v2.ts, refreshes deduplicates by broker URL and refresh token, but .finally(() => refreshes.delete(key)) removes the entry as soon as the broker response completes. OpenCode @opencode/core@2.0.27 persists the returned credential after the plugin's refresh() promise resolves, leaving a gap between those events.

Evidence

An adversarial test in the final auth review gated host persistence, then started a second resolve after the first broker response. The second request replayed the old token and received 401; the first request succeeded and persisted the rotated token, so the next resolve recovered. The reproducer lived in scratch and is not committed.

This demonstrates a transient failing tool call when the broker rejects a replay. Supabase refresh-token single-use or token-family invalidation behavior remains unverified; this issue does not claim that replay invalidates the account.

Related: PR #87, reviewed at 008c8d7ff4b3a2e01cf2b1867913a9bb6598197e against OpenCode 2.0.27.

Expected behavior

Concurrent resolves using the same stale credential across the host persistence window should not issue duplicate broker refresh requests. Any mitigation must preserve per-account and per-Location isolation, clean up retained results safely, and propagate refresh failures to waiting callers.

Acceptance criteria

  • Add a deterministic test that gates host credential persistence after the first broker response, then starts a second resolve with the still-stored old token.
  • The second resolve does not issue a duplicate refresh request for that old token.
  • Both resolves converge on the persisted refreshed credential, or return a clear, retryable error if the host cannot safely share the result.
  • Failed refreshes release waiters and do not leave stale success entries.
  • Tests cover separate credentials and Locations to prevent cross-account result sharing.

Severity

Medium reliability issue. The reproduced impact is a transient tool failure during a narrow race; account reauthorization is not established.

Activity

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

    bugSomething isn't workingpriority:p2Valid but lower urgencystatus:triagedReviewed and ranked

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions