GraphQL Proxy
POST /api/github/graphql is a restricted relay to the GitHub GraphQL API. It uses the user OAuth token and only accepts a fixed set of named operations — you can’t send arbitrary queries through it.
Request contract
POST /api/github/graphql
Authorization: via gh_session cookie or Bearer header
{
"operationName": "ViewerProfile", // REQUIRED — must be in allowlist
"query": "...", // optional — server may substitute its own
"variables": {} // optional
}The allowlist
Exactly 7 operations are exposed (USER_GITHUB_GRAPHQL_OPERATIONS in lib/github/operations/registry.ts):
| Operation | What it returns |
|---|---|
ViewerProfile | Current user + their org memberships + admin status |
SearchUsers | GitHub user search (debounced at 300ms client-side) |
OrganizationSummary | Org metadata and entity counts |
OrgRepositories | Paginated list of repos in an org |
OrgTeamsData | Teams and their members for an org |
OrgScoringData | Org-level scoring data for the ingest pipeline |
OrganizationRepositories | Repos with metadata for the repository leaderboard |
Sending any other operationName returns 400.
Query resolution
The registry holds a OPERATION_QUERY_FALLBACKS map with server-owned query text for each operation. Resolution order:
- If a server fallback exists for the operation → use it (client-supplied
queryis discarded) - If no fallback exists → use client-supplied
querytext - If neither exists → 400
In practice, the server always has fallbacks for the 8 allowed operations. The client’s query field is never used — the server’s version wins. This prevents the proxy from being abused for arbitrary field enumeration or introspection.
Authentication
The proxy uses the user OAuth token from the session, not an installation token:
getRequestSession() → decrypt token → createUserOctokit(accessToken) → octokit.graphql()Server-side ingest queries in lib/github/fetch-graphql.ts run with installation tokens and bypass the proxy entirely. They are not constrained by the 8-operation allowlist.
Caching
The proxy has its own dedicated cache layer (lib/github/graphql-response-cache.ts):
| Cache tier | Strategy | TTL |
|---|---|---|
| In-memory | LRU(64) | 90 seconds |
| Redis | Standard key, per operation + variables | 90 seconds |
Redis (OrgRepositories) | Separate key | 5 minutes |
OrgRepositories has a longer TTL because repository lists change infrequently. Cache hits skip the GitHub API call entirely, reducing rate limit pressure for frequent queries like org member searches.
Error responses
| Status | Condition |
|---|---|
| 400 | Missing or empty operationName |
| 400 | operationName not in allowlist |
| 400 | No query text available (no fallback + none supplied) |
| 401 | No session or session expired |
| 500 | GitHub GraphQL returned an error |
Adding a new operation
- Add the name to
USER_GITHUB_GRAPHQL_OPERATIONSinlib/github/operations/registry.ts - Write the GraphQL query string and add it to
OPERATION_QUERY_FALLBACKS - Create a frontend hook in
hooks/queries/that calls the proxy - Add a query key in
hooks/query-keys.ts
The proxy handler and executeGithubGraphql() don’t need changes — they’re driven entirely by the registry.
Server-only queries
The ingest pipeline uses additional queries that live in lib/github/queries/ and are called directly from lib/github/fetch-graphql.ts. These use installation tokens and are never exposed through the proxy. They include:
repoScoringDataCore— commit data for scoringrepoScoringDataPrMeta— PR metadata (merge status, linked issues)repoScoringDataReviews— review data with statesrepoScoringDataIssueLinks— issue-to-PR relationships
These queries handle pagination, enrichment (PR metadata, issue links, review details), and error handling that the public proxy doesn’t need to expose.