Skip to content

fix(pi-cursor-agent): keep refresh credential across token exchange - #30

Open
leaveye wants to merge 1 commit into
sudosubin:mainfrom
leaveye:fix/cursor-agent-oauth-refresh
Open

leaveye wants to merge 1 commit into
sudosubin:mainfrom
leaveye:fix/cursor-agent-oauth-refresh

Conversation

@leaveye

@leaveye leaveye commented Aug 31, 2026

Copy link
Copy Markdown

Problem

With pi-cursor-agent, the OAuth refresh flow breaks permanently after the first access-token expiry:

  1. AuthManager.refresh calls exchange_user_api_key with the stored refresh credential and receives a new JWT access + refresh pair.
  2. The JWT refresh token is stored back as the refresh credential.
  3. On the next expiry, exchange_user_api_key is called with that JWT, but the endpoint only accepts a crsr_ API key (or the original stored value). It returns 401 Invalid User API Key.
  4. Every subsequent refresh fails, so pi reports OAuth refresh failed for cursor-agent.

Verified against https://api2.cursor.sh/auth/exchange_user_api_key:

Credential sent as Bearer Result
crsr_... API key 200
JWT access token 401 Invalid User API Key
JWT refresh token 401 Invalid User API Key

Fix

Preserve the original refresh credential when returning refreshed tokens instead of replacing it with the JWT returned by the exchange. A stored crsr_ API key stays valid across refreshes, so the flow recovers automatically after each access-token expiry.

Tests

Added test/lib/auth.test.ts covering:

  • happy path: refresh returns the new access token while keeping the stored crsr_ refresh credential (and asserts the exchange is invoked with it)
  • access-only fallback path
  • failure path (both credentials fail → descriptive error)

Verification

  • npm run lint ✅
  • npm run typecheck ✅
  • npm test ✅ (37 pass, including the 3 new tests)
  • End-to-end: token forced to expired state, then a request to cursor-agent/composer-2.5 succeeded and auth.json was refreshed (expires updated, refresh credential unchanged).

The refresh flow used the JWT returned by exchange_user_api_key as the
stored refresh credential, but that endpoint only accepts a crsr_ API
key (or the original stored refresh value). Subsequent refreshes then
failed with 401 / "Invalid User API Key" every time the access token
expired, leaving pi unable to recover the cursor-agent credentials.

Preserve the original refresh credential when returning refreshed tokens
so refresh keeps working across expirations. Add unit tests covering the
happy path, the access-only fallback, and the failure path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant