Skip to content

Enterprise org discovery calls a non-existent REST endpoint (always 404s, falls back to /user/orgs) #13

Description

@robpitcher

Summary

listEnterpriseOrgs() in app/src/lib/github/copilot-api.ts tries GET /enterprises/{slug}/organizations first, but this REST endpoint does not exist. GitHub only exposes enterprise organization listing via the GraphQL API (enterprise.organizations). The REST call therefore returns 404 Not Found on every sync — even with full admin:enterprise / manage_billing:enterprise scopes — and the code falls back to GET /user/orgs.

Impact

  • Misleading warning on every ingest: Enterprise organizations endpoint unavailable (... 404 Not Found ...). This may indicate the enterprise slug or team slug is incorrect or your PAT does not have access. None of that is true — the endpoint simply doesn't exist in REST.
  • Wasted API request: one guaranteed-404 round-trip per org-discovery call.
  • Latent under-counting (the real bug): the /user/orgs fallback only lists orgs the PAT user is a member of. In an enterprise containing orgs the PAT user does not belong to, those orgs are silently dropped → under-counted Copilot usage metrics. In small enterprises where the PAT user happens to be in every org, results look correct, masking the bug.

Origin

Introduced in commit 0b266ce ("Refactor metrics/user-management to official GitHub APIs"). Before that commit, listEnterpriseOrgs used only GET /user/orgs. The refactor wrapped it in the non-existent enterprise REST endpoint, making it effectively a no-op regression — identical results, plus a 404 and a misleading warning on every sync. Unrelated to PR #12 (just first noticed while testing that branch).

Evidence

  • GET /enterprises/dev-joy-emu/organizations → 404 Not Found, even with a PAT holding admin:enterprise.
  • GraphQL works and returns all orgs authoritatively:
    query($slug: String!) {
      enterprise(slug: $slug) {
        organizations(first: 100) {
          totalCount
          nodes { login databaseId }
          pageInfo { hasNextPage endCursor }
        }
      }
    }
    → returns djemu-sbx, djemu-strict (regardless of which orgs the PAT user personally belongs to).

Proposed fix

  • Replace the dead REST call in listEnterpriseOrgs with the GraphQL enterprise(slug) { organizations } query, paginated via pageInfo/endCursor, mapping to { login, id: databaseId }.
  • Keep GET /user/orgs as a genuine fallback for non-enterprise tokens / when GraphQL returns NOT_FOUND (token lacks enterprise access).
  • Update the warning text so it only fires for the real fallback case, not on every run.

Affected code

  • app/src/lib/github/copilot-api.ts
    • listEnterpriseOrgs() (~L468–510) — the non-existent REST endpoint + misleading warning
    • listOrgsViaUserMemberships() (~L435) — the /user/orgs fallback (keep, but demote to true fallback)

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions