Skip to content

Show Copilot Enterprise Usage Without GraphQL Listing - #129

Merged
mstallone merged 3 commits into
mainfrom
fix/copilot-enterprise-billing-without-graphql-scope
Sep 5, 2026
Merged

Show Copilot Enterprise Usage Without GraphQL Listing#129
mstallone merged 3 commits into
mainfrom
fix/copilot-enterprise-billing-without-graphql-scope

Conversation

@mstallone

Copy link
Copy Markdown
Owner

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:enterprise just 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 needs read:enterprise. That listing failed, so the card showed Managed by Your Enterprise even though GET /enterprises/nextbyte/settings/billing/ai_credit/usage already returned this month’s Copilot credits for an org owner.

What this changes

  • After GraphQL listing is denied, derive candidate enterprise slugs from /user/orgs (the org login and its hyphen prefix, so nextbyte-ai also tries nextbyte).
  • Read those enterprises’ Copilot usage over REST. Do not query membership orgs’ own billing endpoints, so a 503 there still cannot take the card down.
  • Cache the slug once it reports Copilot usage, same as the GraphQL path.

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
  • Local refresh after rebuild: Copilot Business showed Credits 1.1K, AI Credits Used 1.1K, Additional Spend $0.00, and no managed-account badge.

Org owners can already read enterprise REST billing; the card was stuck on
Managed by Your Enterprise only because viewer.enterprises needs read:enterprise.
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

Copilot AI 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.

🟡 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/orgs logins (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 .temporarilyUnavailable is 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 .temporarilyUnavailable keeps 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.

Copilot AI 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.

🟡 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

@mstallone
mstallone merged commit 0b8ccdc into main Sep 5, 2026
2 checks passed
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.

2 participants