[Bug]: Cursor limits never appear when the provider is signed in with an API key#19008

opened29d agobyRrcatajar

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. Use nightly 0.0.46-nightly.20261012.3021 (the same readCursorUsageLimits logic is on main at 6524a8d, nightly 0.0.46-nightly.20261013.3053).
  2. Sign in to the Cursor provider through T3. The SDK credential store saves an API key (crsr_… in the apiKey field), not the CLI session JWT. No CURSOR_API_KEY override is set on the provider instance.
  3. On the same machine, have a normal CLI login at ~/.config/cursor/auth.json (Linux / WSL path; accessToken is present).
  4. Open Usage → Limits. Codex and Claude are connected on the same server.

Expected behavior

Cursor shows the plan pools GetCurrentPeriodUsage already returns for this account:

  • Cursor Models (autoPercentUsed) — Grok and Composer
  • Other Models (apiPercentUsed) — Claude, GPT, and Gemini
  • Overall (totalPercentUsed) when both pools are not both present
  • the billing-cycle reset from billingCycleEnd

If the API key cannot read that endpoint, Limits should say so. Hiding the provider with no row and no notice makes it look like Cursor usage was never implemented.

Actual behavior

Codex and Claude limit cards show. Cursor does not. There is no error, stale state, or "usage unavailable" line.

The dashboard call explains the split:

  • POST https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage with the T3-stored API key, connect-protocol-version: 1 and x-cursor-client-type: cli, returns 401.
  • The same request with the CLI auth.json access token returns 200 and a normal planUsage body. On this account that was autoPercentUsed 13.2, apiPercentUsed 84.8, totalPercentUsed 23.4, included-usage copy "You've used 80% of your included usage", and a monthly cycle from 2026-09-23T19:40:34Z to 2026-10-23T19:40:34Z.

So the data exists. T3 never asks for it while this sign-in is active, then the Limits view drops the provider.

Spend history is separate. usage-cursor-account-cache-v1.json on this machine already has about 1,600 Cursor events, read from the CLI login. This report is only the Limits cards.

Impact

Major degradation or frequent failure

Version or commit

0.0.46-nightly.20261012.3021 on the running server. Same behavior in packages/provider-cursor/src/server/usageLimits.ts and packages/shared/src/usageLimits.ts at 6524a8d43795 (0.0.46-nightly.20261013.3053).

Environment

Windows desktop, WSL backend, distro Ubuntu, WSL-only. Server is linux x64 in WSL2 (5.15.167.4-microsoft-standard-WSL2), HOME pointing at the Linux home. Cursor provider enabled. @cursor/sdk 1.0.35. Codex and Claude connected on the same server.

Logs or stack traces

No server log line. The probe returns a normal unsupported snapshot and the UI discards it.

readCursorUsageLimits bails out before any HTTP call when an API key is present and CURSOR_AUTH_TOKEN is not:

ts
1let token = environment.CURSOR_AUTH_TOKEN?.trim();
2// An explicit API key can name a different account from the stored login.
3if (!token && environment.CURSOR_API_KEY?.trim()) {
4 return makeUnavailableUsageLimits({ checkedAt, reason: "unsupported" });
5}

That return has no message. The driver always feeds the credential-store key in as CURSOR_API_KEY:

ts
1readCursorUsageLimits(
2 effectiveConfig,
3 { ...processEnv, CURSOR_API_KEY: apiKey },
4 settings.cursorKeychainUsageEnabled,
5)

apiKey comes from auth.readApiKey, which loads the SDK credential (apiKey: "crsr_…"). status.ts treats a missing key as signed out ("Sign in with Cursor or add CURSOR_API_KEY in provider settings."), so the provider cannot stay authenticated and also fall through to auth.json.

The Limits view then skips the snapshot in both places:

  • collectLimitAccounts drops any snapshot where limitsNotice() is non-null. For reason: "unsupported" that notice is "This account has no subscription limits."
  • collectLimitNotices then skips unsupported on purpose ("Accounts that can never report (API keys) are left out; there is nothing for the user to act on."), so that sentence is never shown either.

Net effect: an API-key Cursor sign-in, which is what the current SDK login stores, produces no Cursor row and no explanation. Codex and Claude remain.

Workaround

None that keeps this sign-in and shows the card. Removing the stored API key marks Cursor signed out, so the limits probe does not run. The source would prefer CURSOR_AUTH_TOKEN over CURSOR_API_KEY when both are set. That was not tried here, and it means copying the CLI session token into provider settings.

The short-context model bug is separate and already tracked in #15788.

Synced from GitHub

Also observed on macOS with T3 nightly 0.0.46-nightly.20261014.3132.

Cursor is browser-authenticated and account usage is enabled, but Cursor is absent from Usage → Limits. The provider snapshot reports unsupported with an empty windows array.

The installed bundle still injects the SDK sign-in credential as CURSOR_API_KEY when calling readCursorUsageLimits. The reader returns unsupported before trying the saved CLI login.

This corroborates the reported issue on macOS. I have not tested PR #19578.

Synced from GitHub

Workaround from the usage docs, tested with the token this server is actually configured to use.

The Cursor provider is still signed in with an API key, which readCursorUsageLimits treats as unsupported and which gets 401 from GetCurrentPeriodUsage. On nightly 0.0.46-nightly.20261014.3118 I set CURSOR_AUTH_TOKEN in the Cursor provider's environment variables to the CLI session token from ~/.config/cursor/auth.json (the accessToken, not the crsr_… API key).

Calling POST https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage with that stored token returns 200 and planUsage.autoPercentUsed, planUsage.apiPercentUsed, planUsage.totalPercentUsed, plus billingCycleEnd. That is the request the limits reader makes when CURSOR_AUTH_TOKEN is set, so it no longer takes the API-key branch that the Limits view drops with no message.

The session token expires 2026-12-09T18:37:28Z. The copy in provider settings will not follow a later rewrite of auth.json. After it expires, the limits probe should surface "Cursor could not read usage limits" until the variable is replaced. Cursor chats keep using the API key, which is a separate credential.

Synced from GitHub

Still present on macOS nightly 0.0.46-nightly.20261015.3145. Signed in with Cursor in the browser and Cursor account usage (cursorKeychainUsageEnabled) turned on. The snapshot reports usageLimits: { windows: [], unavailable: { reason: "unsupported" } }, with no message.

readCursorUsageLimits finishes in about 0.05ms in the trace, so no request is made. The driver injects the sign-in API key as CURSOR_API_KEY, and the early CURSOR_API_KEY return runs before the macOS Keychain branch. The usage toggle has no effect for browser sign-ins, even when a valid cursor-access-token is in the Keychain.

Synced from GitHub

Reproduced on the stable channel: desktop app v0.1.0 (macOS, local desktop-managed server, also 0.1.0). Every report above was from a nightly; v0.1.0 is the first stable release that includes #17349 (merged 2026-10-09, b5e4f1e), so stable users lose the Cursor Limits cards when they update from v0.0.45. This machine updated on 2026-10-15 and the user's words were "I can't see my cursor usage. It used to work."

Setup

  • macOS 27.2.0 (darwin arm64), T3 Code desktop 0.1.0, server 0.1.0 (serverSelfUpdate: desktop-managed)
  • Cursor provider enabled, signed in through T3's browser flow (auth.type: "browser"), default instance, no CURSOR_API_KEY / CURSOR_AUTH_TOKEN anywhere in settings
  • cursorKeychainUsageEnabled: true; a valid Cursor CLI login in the macOS Keychain (cursor-agent 2026.09.18, cursor-agent status reports logged in)
  • Usage history (CursorUsageAccounts → cursor.com/api/dashboard/get-filtered-usage-events) reads fine from the same Keychain login, so the credential is good; only the Limits probe is skipped

Evidence

~/.t3/caches/cursor.json before the workaround:

json
1"status": "ready",
2"auth": { "status": "authenticated", "type": "browser", "label": "Cursor account", "canLogout": true },
3"version": null,
4"usageLimits": { "checkedAt": "2026-10-20T13:51:01.695Z", "windows": [], "unavailable": { "reason": "unsupported" } }

server.trace.ndjson: every readCursorUsageLimits span today finished in 0.04–0.18 ms with no outgoing request. No request to api2.cursor.sh appears in ~9 hours of retained traces.

12026-10-20T13:11:09Z readCursorUsageLimits 0.064 ms Success
22026-10-20T13:16:10Z readCursorUsageLimits 0.178 ms Success
32026-10-20T13:21:13Z readCursorUsageLimits 0.125 ms Success
42026-10-20T13:51:01Z readCursorUsageLimits 0.042 ms Success

Code path at v0.1.0 (81493ef), same as described above:

  • packages/provider-cursor/src/server/driver.ts:234-238 — readCursorUsageLimits(effectiveConfig, { ...processEnv, CURSOR_API_KEY: apiKey }, settings.cursorKeychainUsageEnabled) where apiKey is the browser-login SDK key from auth.readApiKey
  • packages/provider-cursor/src/server/usageLimits.ts:79-82 — returns unsupported (no message) when CURSOR_API_KEY is set and CURSOR_AUTH_TOKEN is not, before the Keychain branch at L93-108
  • packages/shared/src/usageLimits.ts:345 — Limits view skips providers whose reason is unsupported, so Cursor disappears with no notice

Workaround confirmed on stable

Added CURSOR_AUTH_TOKEN = the Keychain cursor-access-token value as an environment variable on the Cursor provider (Settings → Providers → Cursor). mergeProviderInstanceEnvironment puts it into processEnv, the reader takes the L79 branch, and the next probe made a real request (readCursorUsageLimits 2763 ms). The cache then held the expected windows:

json
1"usageLimits": { "checkedAt": "2026-10-20T14:23:17.839Z", "windows": [
2 { "id": "apiPercentUsed", "kind": "monthly", "label": "Other Models", "usedPercent": …, "resetsAt": "2026-11-03T23:16:39.000Z" },
3 { "id": "autoPercentUsed", "kind": "monthly", "label": "Cursor Models", "usedPercent": …, "resetsAt": "2026-11-03T23:16:39.000Z" },
4 { "id": "totalPercentUsed", "kind": "monthly", "label": "Overall", "usedPercent": …, "resetsAt": "2026-11-03T23:16:39.000Z" } ] }

Same caveats as noted above: the token sits in plain text in settings.json and will not follow CLI refreshes.


Posted via t3 triage by Claude Code running Claude Fable 5.1 (claude-fable-5-1). Triage CLI 0.1.0.

Synced from GitHub

Same here on stable 0.1.3, macOS. Cursor signed in through the browser, "Cursor account usage" turned on, and Limits still comes back unsupported with empty windows. The toggle doesn't change anything since the CURSOR_API_KEY check returns first.

The account itself is fine, GetCurrentPeriodUsage with the Cursor app's session token gives me all three pools plus the billing cycle.

One thing worth knowing for #21228: my CLI token in the Keychain had expired on Oct 19 and I never noticed, because the browser sign-in kept working. So just falling back to the Keychain wouldn't have fixed it for me. Exchanging the instance's own credential like #21228 does sounds like the right call.

Happy to try a build of #21228 against my account if you still need someone to check it on a real one.

Synced from GitHub

Also reproduced on Linux with server nightly 0.1.4-nightly.20261028.3824, and main still has the early return

Environment

  • Server: t3 serve, version 0.1.4-nightly.20261028.3824 (Linux x64, Node 26). I checked out tag v0.1.4-nightly.20261028.3824.
  • Surface: the user reports it in the desktop app. The desktop app version is unknown.
  • Cursor provider: signed in through T3's browser flow. No apiEndpoint override and no CURSOR_* variables in the server's environment (I checked variable names only).
  • A normal CLI login exists at ~/.config/cursor/auth.json, with an accessToken and a refreshToken.

Evidence

  • The Cursor provider status cache has auth.status: "authenticated" and usageLimits: { windows: [], unavailable: { reason: "unsupported" } }, with no message.
  • In server.trace.ndjson, readCursorUsageLimits finishes in about 0.2 ms with a success exit. No request to api2.cursor.sh appears, so it returns before the HTTP call.
  • driver.ts passes the sign-in key to the reader as CURSOR_API_KEY, and usageLimits.ts:81 (if (!token && environment.CURSOR_API_KEY?.trim())) returns unsupported on it. Per the code comment this guards against a different account, but it also fires for T3's own stored key.
  • The same line is still on main.

Workaround confirmed I sent POST https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage with the accessToken from auth.json and the connect-protocol-version: 1 and x-cursor-client-type: cli headers. It returned 200 with planUsage.totalPercentUsed, autoPercentUsed and apiPercentUsed, plus billingCycleEnd.

I then added CURSOR_AUTH_TOKEN (that accessToken) to the Cursor instance's environment in settings.json. Within a few seconds the provider cache had three windows: Cursor Models, Other Models and Overall, all resetting on the billing-cycle end date. So the workaround in the earlier comment also works on this build.

Notes for a fix

  • The crsr_… key and the CLI session token are different credentials. When T3 holds only its own sign-in key, the reader should use the CLI auth.json token, or at least show a visible "usage unavailable" message instead of an unsupported state with no message.
  • The "Cursor account usage" toggle (cursorKeychainUsageEnabled) doesn't help here, because the CURSOR_API_KEY check returns first.

Written by Claude Sonnet 5.5 in a Claude Code t3 triage session.

Synced from GitHub

Also reproduced on the latest stable v0.1.5: macOS arm64 (Darwin 25.6.0), desktop app (T3 Code Beta) against its bundled local server, also 0.1.5.

  • The Cursor provider is signed in through T3's browser flow (auth.type: "browser"). No CURSOR_API_KEY is set on the instance.
  • cursorKeychainUsageEnabled: true, and macOS Keychain access for Cursor is allowed.
  • The provider snapshot has usageLimits: { windows: [], unavailable: { reason: "unsupported" } } with no message.
  • In server.trace.ndjson, readCursorUsageLimits finishes in about 0.08 ms with Success and makes no request to api2.cursor.sh.

I checked the code at tag v0.1.5 (6d450a4). driver.ts:237-241 still passes the sign-in key as CURSOR_API_KEY, and usageLimits.ts:78-80 returns unsupported before it reaches the Keychain branch, so the toggle has no effect. Neither usageLimits.ts nor the call site in driver.ts has changed on main since then, so v0.1.6-nightly is affected too. Open fixes: #21228 and #26670.

The user said Cursor limits used to show up and stopped at some point. They don't remember when, but it fits the move to v0.1.x. Claude limits still work.

Workaround: set CURSOR_AUTH_TOKEN in the Cursor provider instance's environment variables to the CLI token from the Keychain item cursor-access-token / cursor-user.

Triage by Claude Opus 5.5 (claude-opus-5-5) in Claude Code, via t3 triage.

Synced from GitHub

Reproduced on stable 0.1.2, Linux x64 (CachyOS). The Cursor instance uses T3's browser sign-in, and ~/.config/cursor/auth.json holds a valid CLI login. In server.trace.ndjson, all 12 readCursorUsageLimits spans finish in 0.15–1.5 ms with no HTTP child span. That matches the early CURSOR_API_KEY return in usageLimits.ts.

I compared the three open fixes against main at ac8dce0.

Recommendation: #26670 is the only one that reads the instance's own account. It is also the only one that works without a host CLI login.

#26670#28449#24443
ApproachExchange the stored key at /auth/exchange_user_api_key, then call GetCurrentPeriodUsageFall back to the host CLI login, but only on the default endpointDrop the browser key and fall back to the host CLI login or Keychain
Account shownThe instance's own sign-inThe host CLI login (may be a different account, see #15906)The host CLI login
Works without auth.json / Keychain loginyesnono
Custom apiEndpointuses the login's own https origin, redirects rejectedgated, nothing sentsends the auth.json token to the custom endpoint (the file branch has no endpoint check; only the Keychain branch does)
Its own tests at PR head24/24 pass21/21 passdriver browser test times out (60 s) on Linux, and the fork's CI Test job failed
Merged onto main (keeping main's driver.test.ts)119/119 pass, tsc clean117/117, tsc clean117/117, tsc clean
Merge conflictdriver.test.ts onlydriver.test.ts onlydriver.test.ts only

Live checks against api2.cursor.sh (to back the exchange approach; tokens not shown):

  • GetCurrentPeriodUsage returns 200 with full planUsage for both x-cursor-client-type: cli and sdk. So #26670's sdk header is accepted.
  • POST /auth/exchange_user_api_key exists and validates user API keys: a bogus key returns 401 {"message":"Invalid User API Key"}, and so does a CLI session JWT.
  • @cursor/sdk 1.0.38 makes the same exchange (POST {backend}/auth/exchange_user_api_key, bearer user key, body {}), as the PR says.
  • Not verified: the full exchange → usage round trip with a real T3-stored crsr_ key. Nobody in this thread or on the PR has run it yet.

Open points on #26670 before merge:

  1. Rebase is needed, but only driver.test.ts conflicts. The production code merges cleanly.
  2. A stored login short-circuits before CURSOR_AUTH_TOKEN is read (usageLimits.ts:139 vs :148). The workaround from this thread is then silently ignored. An explicit override should probably win.
  3. A 401 from GetCurrentPeriodUsage itself still ends up as probeFailed. Only an exchange 401 maps to "sign in again" (the CodeRabbit point is still open).
  4. Minor: the key is exchanged again on every refresh (about every 5 minutes). The access token could be cached until it expires.

Once #26670 lands, #28449 and #24443 can be closed.

Review done with Claude Code.