Skip to content

Fix enterprise/team Cursor usage via usage-summary fallback#832

Open
andyli-star wants to merge 2 commits into
robinebers:mainfrom
andyli-star:fix/enterprise-usage-summary-fallback
Open

Fix enterprise/team Cursor usage via usage-summary fallback#832
andyli-star wants to merge 2 commits into
robinebers:mainfrom
andyli-star:fix/enterprise-usage-summary-fallback

Conversation

@andyli-star

@andyli-star andyli-star commented Jul 2, 2026

Copy link
Copy Markdown

TL;DR

Enterprise and team Cursor accounts no longer fail with "Enterprise usage data unavailable" — OpenUsage now falls back to cursor.com/api/usage-summary when dashboard usage is empty.

Closes #829

What was happening

  • GetCurrentPeriodUsage returns no usable planUsage for Enterprise/team (TOKEN_BASED_CONTRACT) accounts
  • The existing /api/usage fallback also returns null maxRequestUsage
  • OpenUsage surfaced "Enterprise usage data unavailable. Try again later."

What this changes

  • Add GET cursor.com/api/usage-summary client method (same cookie auth as other REST calls)
  • When enterprise/team fallback is triggered, try usage-summary before request-based /api/usage
  • Map teamUsage.pooled and teamUsage.onDemand as dollar progress meters (cents → dollars)
  • Map individualUsage.overall when pooled team usage is absent
  • Parse auto/API usage percents from dashboard display messages
  • Use billingCycleStart / billingCycleEnd for reset countdowns
  • Unit tests for mapper and provider integration path
  • Document the new endpoint in docs/providers/cursor.md

Heads-up

  • Usage-summary is only attempted for enterprise/team accounts where the existing fallback logic already fires; request-based /api/usage remains the fallback for other account types.

Tests

  • CursorUsageMapperTests.testMapsEnterpriseUsageSummary
  • CursorUsageMapperTests.testShouldUseUsageSummaryFallbackForEnterprise
  • CursorProviderTests.testRefreshFallsBackToUsageSummaryForEnterprise
  • Full suite not run locally (Xcode license not accepted on build machine); CI should verify.

Test plan

  • Enterprise account: refresh shows Total usage (pooled team spend) instead of error
  • Team account with empty planUsage: usage-summary metrics appear
  • Pro/individual accounts: unchanged behavior (no usage-summary call)
  • On-demand meter shows when teamUsage.onDemand has a limit
  • Billing cycle reset countdown matches dashboard dates

Made with Cursor

Confidence Score: 4/5

Safe to merge; the new code path only activates for enterprise/team accounts that already surfaced an error, and any failure falls back to the existing request-based path.

The implementation is consistent with existing codebase patterns and the tests verify both the mapper logic and the provider integration path. The one concern is that try? on usageSummaryResult swallows all errors — including connectivity failures — before falling through to requestBasedResult, which can obscure the true cause of failure for enterprise users when cursor.com is unreachable. There are no data-loss or auth-boundary issues.

The error-suppression in CursorProvider.swift lines 112-115 is worth a second look to decide whether connectivity errors should propagate instead of being silently absorbed into the subsequent fallback.

Fix All in Cursor Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
Sources/OpenUsage/Providers/Cursor/CursorUsageMapper.swift:311-321
The `NSRegularExpression` is compiled from scratch on every call to `percentFromDisplayMessage`. The pattern is static, so a one-time `static let` avoids repeated compilation.

```suggestion
    private static let percentRegex = try? NSRegularExpression(pattern: #"(\d+(?:\.\d+)?)\s*%"#)

    private static func percentFromDisplayMessage(_ message: String?) -> Double? {
        guard let message,
              let regex = percentRegex,
              let match = regex.firstMatch(in: message, range: NSRange(message.startIndex..., in: message)),
              let range = Range(match.range(at: 1), in: message)
        else {
            return nil
        }
        return Double(message[range])
    }
```

### Issue 2 of 2
Sources/OpenUsage/Providers/Cursor/CursorProvider.swift:112-115
**Silent suppression of connectivity errors from `usageSummaryResult`**

`try?` discards any error thrown by `usageSummaryResult` — including `CursorUsageError.connectionFailed` if `cursor.com` is reachable from the gRPC endpoint (`api2.cursor.sh`) but not from the REST endpoint. When that happens the provider silently falls through to `requestBasedResult`, which will also fail and ultimately surface "Enterprise usage data unavailable" rather than "Connection failed". This is consistent with the `shouldTryGenericRequestFallback` block above, but there the suppressed path is a speculative optimistic attempt; here it is the primary enterprise code path. Consider propagating at least connection-class errors instead of uniformly swallowing them.

Reviews (1): Last reviewed commit: "Fix enterprise/team Cursor usage via usa..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Closes robinebers#829

When GetCurrentPeriodUsage returns unusable planUsage for enterprise
or team accounts, fetch cursor.com/api/usage-summary (the same endpoint
the dashboard uses) before falling back to request-based /api/usage.
Maps pooled/on-demand team spend and individual overall usage as dollar
meters, auto/API percents from display messages, and billing cycle dates.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment on lines +311 to +321
private static func percentFromDisplayMessage(_ message: String?) -> Double? {
guard let message else { return nil }
let pattern = #"(\d+(?:\.\d+)?)\s*%"#
guard let regex = try? NSRegularExpression(pattern: pattern),
let match = regex.firstMatch(in: message, range: NSRange(message.startIndex..., in: message)),
let range = Range(match.range(at: 1), in: message)
else {
return nil
}
return Double(message[range])
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The NSRegularExpression is compiled from scratch on every call to percentFromDisplayMessage. The pattern is static, so a one-time static let avoids repeated compilation.

Suggested change
private static func percentFromDisplayMessage(_ message: String?) -> Double? {
guard let message else { return nil }
let pattern = #"(\d+(?:\.\d+)?)\s*%"#
guard let regex = try? NSRegularExpression(pattern: pattern),
let match = regex.firstMatch(in: message, range: NSRange(message.startIndex..., in: message)),
let range = Range(match.range(at: 1), in: message)
else {
return nil
}
return Double(message[range])
}
private static let percentRegex = try? NSRegularExpression(pattern: #"(\d+(?:\.\d+)?)\s*%"#)
private static func percentFromDisplayMessage(_ message: String?) -> Double? {
guard let message,
let regex = percentRegex,
let match = regex.firstMatch(in: message, range: NSRange(message.startIndex..., in: message)),
let range = Range(match.range(at: 1), in: message)
else {
return nil
}
return Double(message[range])
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: Sources/OpenUsage/Providers/Cursor/CursorUsageMapper.swift
Line: 311-321

Comment:
The `NSRegularExpression` is compiled from scratch on every call to `percentFromDisplayMessage`. The pattern is static, so a one-time `static let` avoids repeated compilation.

```suggestion
    private static let percentRegex = try? NSRegularExpression(pattern: #"(\d+(?:\.\d+)?)\s*%"#)

    private static func percentFromDisplayMessage(_ message: String?) -> Double? {
        guard let message,
              let regex = percentRegex,
              let match = regex.firstMatch(in: message, range: NSRange(message.startIndex..., in: message)),
              let range = Range(match.range(at: 1), in: message)
        else {
            return nil
        }
        return Double(message[range])
    }
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor Fix in Claude Code Fix in Codex

Comment on lines +112 to +115
if CursorUsageMapper.shouldUseUsageSummaryFallback(usage: usage, planName: planName),
let mapped = try? await usageSummaryResult(accessToken: currentToken, planName: planName) {
return snapshot(mapped)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Silent suppression of connectivity errors from usageSummaryResult

try? discards any error thrown by usageSummaryResult — including CursorUsageError.connectionFailed if cursor.com is reachable from the gRPC endpoint (api2.cursor.sh) but not from the REST endpoint. When that happens the provider silently falls through to requestBasedResult, which will also fail and ultimately surface "Enterprise usage data unavailable" rather than "Connection failed". This is consistent with the shouldTryGenericRequestFallback block above, but there the suppressed path is a speculative optimistic attempt; here it is the primary enterprise code path. Consider propagating at least connection-class errors instead of uniformly swallowing them.

Prompt To Fix With AI
This is a comment left during a code review.
Path: Sources/OpenUsage/Providers/Cursor/CursorProvider.swift
Line: 112-115

Comment:
**Silent suppression of connectivity errors from `usageSummaryResult`**

`try?` discards any error thrown by `usageSummaryResult` — including `CursorUsageError.connectionFailed` if `cursor.com` is reachable from the gRPC endpoint (`api2.cursor.sh`) but not from the REST endpoint. When that happens the provider silently falls through to `requestBasedResult`, which will also fail and ultimately surface "Enterprise usage data unavailable" rather than "Connection failed". This is consistent with the `shouldTryGenericRequestFallback` block above, but there the suppressed path is a speculative optimistic attempt; here it is the primary enterprise code path. Consider propagating at least connection-class errors instead of uniformly swallowing them.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Cursor Fix in Claude Code Fix in Codex

@andyli-star

Copy link
Copy Markdown
Author

Closing for now — still iterating on enterprise usage mapping (adding individual monthly usage). Will reopen when ready.

@andyli-star andyli-star closed this Jul 2, 2026
Show individualUsage.overall as Total Usage (matching Cursor dashboard),
expose teamUsage.pooled as a separate Team Pool metric, and document the
mapping for enterprise usage-summary accounts.

Co-authored-by: Cursor <cursoragent@cursor.com>
@andyli-star andyli-star reopened this Jul 3, 2026
@robinebers robinebers added needs-approved-issue External PR needs an open issue with status:approved needs-screenshots Visual change needs before and after screenshots labels Jul 3, 2026 — with Cursor

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Closing for two reasons:

  1. No approved issue — OpenUsage reviews external PRs only when they implement an issue a maintainer has already approved with the approved label. This links #829, which is open but not labeled approved. Please wait for approval, then reopen this (or open a focused PR) linking it with Fixes #<issue>.
  2. Missing screenshots — This changes user-visible behavior (usage meters and layout defaults), but the description has no images and no Screenshots section saying "Not applicable". Please add before/after screenshots when you resubmit.

Closing for now — happy to take a look once it's linked to an approved issue and includes screenshots.


Powered by Cursor Automations

Open in Web View Automation 

Sent by Cursor Automation: OpenUsage PR Gatekeeper

@robinebers robinebers added keep-open Do not close automatically and removed needs-approved-issue External PR needs an open issue with status:approved needs-screenshots Visual change needs before and after screenshots labels Jul 3, 2026
@robinebers

Copy link
Copy Markdown
Owner

Hi @andyli-star — thanks a lot for digging into this and for the PR, genuinely appreciated. 🙏🏻

I have to be upfront: we landed on the same approach in parallel and just opened #846, which covers the same core fix (usage-summary fallback for enterprise/team via the existing session-token cookie, pooled/on-demand mapped as dollar meters, request-based path as the safety net). So rather than merge both, I'd like to consolidate on #846 and close this one — but your enterprise account is exactly the verification we're missing, so your input would be very valuable there.

A few notes on why I'm not taking this version as-is:

  • Team Pool widget — adding a new metric means new placement defaults (enabled/pinned/order) that every non-team user carries in their customize list, so I'd rather not introduce one for this. In fix(cursor): fall back to /api/usage-summary for enterprise/team accounts #846 the pooled meter is Total Usage when limitType is team, which matches what the dashboard treats as the binding limit.
  • Percent parsing from display messagesautoModelSelectedDisplayMessage and friends are UI strings Cursor can reword anytime, so regex-scraping them is too fragile to ship. If the full response contains structured fields for these, I'd happily map them though!
  • Credits/Stripe/spend tiles in the enterprise path — plausible, but it's a lot of extra surface on an account type we can't test; I'd rather add those as a follow-up once the basic meters are confirmed working.

Two asks, if you're up for it:

  1. Could you paste a full (redacted) /api/usage-summary response from your account? Yours clearly has fields the issue fixture didn't show, and that would tell us whether structured auto/API percent data exists.
  2. Would you be willing to test a build of fix(cursor): fall back to /api/usage-summary for enterprise/team accounts #846 and confirm the meters look right against your dashboard?

Thanks again — this issue would still be open without your and the reporter's legwork.

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

Labels

docs keep-open Do not close automatically provider tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enterprise/team accounts fail: usage-summary endpoint not used as fallback

3 participants