Show Copilot Enterprise Usage Without GraphQL Listing - #129
Conversation
Org owners can already read enterprise REST billing; the card was stuck on Managed by Your Enterprise only because viewer.enterprises needs read:enterprise.
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
There was a problem hiding this comment.
🟡 Changes recommended
The new enterprise-direct fallback path currently treats transient /user/orgs and enterprise-usage 429/5xx failures as “managed” (non-transient), which can overwrite last-good snapshots instead of preserving them via .temporarilyUnavailable.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR improves Copilot enterprise-direct seat billing discovery by removing the hard dependency on GraphQL viewer.enterprises (and its read:enterprise scope) when enterprise usage can still be read via the REST billing endpoint once an enterprise slug is known.
Changes:
- Adds a fallback path for enterprise-direct seats: when enterprise listing is denied, derive candidate enterprise slugs from
/user/orgslogins (and hyphen-prefixes) and probe enterprise REST usage. - Refactors enterprise-direct billing lookup into a dedicated extension file and adds slug-derivation helpers + unit tests.
- Updates Copilot provider docs and in-code comments to reflect the new discovery behavior and guardrails (avoid probing membership org billing endpoints).
File summaries
| File | Description |
|---|---|
| Tests/RunwayTests/CopilotProviderTests.swift | Adds tests for slug derivation and enterprise-direct fallback behavior when GraphQL listing is denied. |
| Sources/Runway/Providers/Copilot/CopilotUsageMapper.swift | Clarifies semantics of hasNoSeatOrganization with respect to membership-derived discovery vs billing endpoint probing. |
| Sources/Runway/Providers/Copilot/CopilotProvider.swift | Updates billing-token selection rationale for enterprise-direct seats and the new discovery strategy. |
| Sources/Runway/Providers/Copilot/CopilotOrgBillingMapper.swift | Adds candidateEnterpriseSlugs(fromOrgLogins:) helper for membership-derived enterprise slug guesses. |
| Sources/Runway/Providers/Copilot/CopilotOrgBillingLookup.swift | Refactors enterprise-direct billing lookup out and adjusts access control to support the new extension file. |
| Sources/Runway/Providers/Copilot/CopilotOrgBillingClient.swift | Updates comments documenting fallback behavior when GraphQL enterprise listing is denied. |
| Sources/Runway/Providers/Copilot/CopilotEnterpriseDiscovery.swift | Updates comments to reflect the membership-derived slug fallback when listing is denied. |
| Sources/Runway/Providers/Copilot/CopilotEnterpriseDirectBilling.swift | New enterprise-direct billing lookup implementation with membership-derived slug probing and caching. |
| docs/providers/copilot.md | Documents the new behavior when read:enterprise is missing and enterprise slugs are derived from /user/orgs. |
Review details
Suppressed comments (1)
Sources/Runway/Providers/Copilot/CopilotEnterpriseDirectBilling.swift:123
- In
probeEnterpriseWideSlugs, transient failures (429/5xx) during membership-derived probing currently fall through to.managed(...)(because.temporarilyUnavailableis only returned for.singleListedSlug). That can cause the card to flip from previously displayed enterprise usage/zeros to a managed-account badge on a transient GitHub outage; treating any transient enterprise-usage failure as.temporarilyUnavailablekeeps the provider’s “preserve last-good snapshot on transient failures” behavior consistent.
// Guessed slugs must not turn a membership 503 into a failed card. GraphQL-listed
// enterprises still surface a transient outage so last-good totals can stay on screen.
if sawTransientFailure, emptyAcceptance == .singleListedSlug {
return .temporarilyUnavailable
}
return .managed(provenEnterpriseAssociation: false)
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
A 429 or 5xx on /user/orgs or a guessed enterprise usage report must fail the refresh instead of publishing the managed-account badge over real numbers.
There was a problem hiding this comment.
🟡 Changes recommended
The membership-derived enterprise slug probing can trigger a very large number of REST calls (up to ~200), creating avoidable refresh latency and elevated risk of API rate limiting.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Lite
…e-billing-without-graphql-scope
TL;DR
Enterprise-direct Copilot seats now show organization credit totals with the GitHub CLI token you already have. The card no longer asks for
read:enterprisejust to discover the enterprise slug.What was happening
GitHub Copilot Business assigned directly by an enterprise returns an empty org list. Runway listed enterprises through GraphQL (
viewer.enterprises), which needsread:enterprise. That listing failed, so the card showed Managed by Your Enterprise even thoughGET /enterprises/nextbyte/settings/billing/ai_credit/usagealready returned this month’s Copilot credits for an org owner.What this changes
/user/orgs(the org login and its hyphen prefix, sonextbyte-aialso triesnextbyte).Heads-up
This still does not grant billing to a token that cannot read the enterprise usage report. It only removes the extra GraphQL scope that was blocking discovery.
Tests
swift test --filter CopilotProviderTests --filter CopilotUsageMapperTests --filter CopilotOrgBillingMapperTests