fix(github): fence repository pagination to selected connection (#48)
Changelog
Fixed: An interrupted GitHub repository listing can no longer advance its cursor onto a different installation when connections are added, revoked, reordered, or rescoped.
Change
- Replace index-based
{installation, page}cursors with bounded v2 opaque cursors encoding an exact selectedconnectionId, page, and SHA-256 fence. - Compute the fence from the authenticated principal, normalized query, current ordered connection identities, selected-connection state and team integration policy. A changed selection returns
409 cursor_staleand requests a fresh list. - Re-check policy/connection/current membership after the GitHub request on every page, including pages with no matching repositories. Reusing a stale cursor never silently changes accounts.
- Keep the existing GitHub App, connection/token owner, auth, one-upstream-page limit, and response shape. Legacy index cursors return 400 and require a fresh list; clients already handle
request.invalidfor malformed cursors.
Regression
The first commit (dcbc9b820e3e97e558c50e7d8d2c3b76e26a4cd1) adds backend/apps/api/test/github-repos-cursor.test.ts: same-connection next page, next exact installation, changed order/revoke/insert, different principal/query/policy, invalid/oversized legacy cursors, and no connections. It initially fails to import the missing helper. The implementation is the next commit. Run cd backend && bun --filter @cmux/api test -- github-repos-cursor (or the matching backend Vitest invocation).
Observed / Not run
Observed in #27025 head cbbf1f3ca66063543866aa4bcdc4194686e7951e: the repo cursor used a numeric position in Object.values(state.connections); revocation/reordering can select another account mid-pagination. This PR is stacked on #27025 for an isolated review and changes two files. Live provider calls, deployment and account mutations were untouched. Local backend Vitest/TS typecheck are unavailable here; CI checks are the verification gate.
Related: cmux-platform issue #48. Stable account-aware resource-selection projection across the platform API remains with #48’s existing owner lane.
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes GitHub repository pagination so an interrupted listing can no longer silently switch to a different connection when connections are added, revoked, reordered, or rescoped.
- Replaces index-based
{installation, page}cursors with v2 opaque cursors that carry the exact selectedconnectionId, page, and a fence hash of the principal, query, policy, and ordered connection list. - Returns
409 cursor_stalewhen the fence changes, and400 request.invalidfor legacy or malformed cursors; clients already handlerequest.invalid. - Re-checks policy and connection membership after every GitHub request, including pages with no matching repositories.
- Adds integration coverage that calls the authorized
ConnectionDOpaging method directly (the public route stays off in the test deployment) and verifies a stale cursor is refused before any further upstream call.