Skip to content

feat(api): GitHub App installation tokens join the token pool - #315

Merged
vn7n24fzkq merged 2 commits into
mainfrom
feat/github-app-token
Jul 29, 2026
Merged

feat(api): GitHub App installation tokens join the token pool#315
vn7n24fzkq merged 2 commits into
mainfrom
feat/github-app-token

Conversation

@vn7n24fzkq

@vn7n24fzkq vn7n24fzkq commented Jul 28, 2026

Copy link
Copy Markdown
Owner

What

App installation tokens become first-class token-pool slots, ahead of the env PATs. This lets the hosted service run on App quota so the personal PAT can retire from the pool (#308 follow-up).

  • api/utils/github-app-token.ts mints installation tokens from GH_APP_ID + GH_APP_PRIVATE_KEY (PEM verbatim, \n-escaped, or base64) with a hand-rolled RS256 JWT via Node crypto — no new dependency. Tokens cache in module memory per instance, refresh 5 minutes before expiry, and concurrent callers share one in-flight mint (~2 REST calls per installation per instance per hour). Never logged, never written to Redis.
  • One slot per installation: GH_APP_INSTALLATION_IDS (comma-separated) pins them. Each installation carries its own independent 5,000-point hourly GraphQL quota — installing the App on both backing accounts yields two pools from a single credential. Unset, the first discovered installation is used.
  • Rotation-safe: token acquisition moved inside the retry loop; a failed mint is flagged isTokenAcquisition and rotates to the next slot exactly like a rate-limited PAT. A broken App degrades to PAT service, not to error cards. Username-hash spread now covers appSlots + patSlots.
  • Slot names in logs: GITHUB_APP / GITHUB_APP_<n> / GITHUB_TOKEN_<n> — never the accounts behind them.

Compatibility note

The queries are already integration-token-clean: #313 replaced the stargazers connection (blocked for App/Actions tokens by GitHub's July change) with stargazerCount, and the repro verified the full profile document works with an integration token. New query fields should be probed with an Actions token before shipping.

Rollout

  1. Create the GitHub App (Settings → Developer settings → GitHub Apps): no permissions beyond the default read-only metadata, webhook off.
  2. Install it on both backing accounts; note the two installation ids (from the installation page URL).
  3. Vercel env: GH_APP_ID, GH_APP_PRIVATE_KEY (mark sensitive), GH_APP_INSTALLATION_IDS=<id1>,<id2>.
  4. Deploy, watch logs for Using token source: GITHUB_APP_0/1.
  5. Delete the personal GITHUB_TOKEN env; rename GITHUB_TOKEN_1GITHUB_TOKEN. Pool becomes App×2 + machine PAT ≈ 15k points/hr, personal account fully off the hot path.

Testing

27 suites / 241 tests green, typecheck + lint clean. New coverage: JWT signature verified against a real throwaway RSA keypair (alg, iss, exp≤10min), token caching + refresh-margin re-mint + concurrent-mint coalescing, per-installation isolation, base64/escaped PEM handling, slot arithmetic with and without the App, and handle-card rotation when the mint fails (degrades to PATs; App slot counts in the exhaustion lap).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added support for authenticating with GitHub App installation tokens.
    • GitHub App installations are exposed as unified token “slots” alongside existing personal access token slots.
    • Tokens are cached with automatic refresh and safe reuse across concurrent requests.
  • Bug Fixes
    • Improved card token rotation to mint per-attempt from App slots and gracefully fall back when App token acquisition fails.
    • Rate-limit/auth rotation continues across all available slots before returning an error.
  • Tests
    • Added coverage for GitHub App token minting, caching, slot mapping, and handle-card rotation behavior.

Adds App installation tokens as first-class slots ahead of the env PATs,
so the hosted service can run on App quota and the personal PAT can
retire from the pool (#308 follow-up).

- api/utils/github-app-token.ts mints installation tokens from
  GH_APP_ID + GH_APP_PRIVATE_KEY (PEM verbatim, \n-escaped, or base64)
  with a hand-rolled RS256 JWT via Node crypto — no new dependency.
  Tokens cache in module memory per instance, refresh 5 minutes before
  expiry, and concurrent callers share one in-flight mint (~2 REST calls
  per installation per instance per hour). Never logged, never in Redis.
- One slot PER INSTALLATION: GH_APP_INSTALLATION_IDS (comma-separated)
  pins the installations; each has its own independent 5,000-point
  hourly GraphQL quota, so installing the App on both backing accounts
  yields two pools from one credential. Unset, the first discovered
  installation is used.
- The rotation acquires tokens inside the retry loop: a failed mint is
  flagged isTokenAcquisition and rotates to the next slot exactly like a
  rate-limited PAT — a broken App degrades to PAT service, not to error
  cards. Slot names (GITHUB_APP_n / GITHUB_TOKEN_n) keep logs readable
  without exposing accounts.

Rollout: create the App (no extra permissions; default read-only
metadata is enough — cards read public data only), install it on the
backing accounts, set the three env vars, deploy, then delete the
personal GITHUB_TOKEN env and rename GITHUB_TOKEN_1 to GITHUB_TOKEN.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 42ddb9cb-0bde-4101-9955-2bc358e48630

📥 Commits

Reviewing files that changed from the base of the PR and between bbda368 and 7207986.

📒 Files selected for processing (2)
  • api/utils/github-app-token.ts
  • tests/utils/handle-card.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/utils/handle-card.test.ts
  • api/utils/github-app-token.ts

📝 Walkthrough

Walkthrough

Adds GitHub App installation token minting with caching and concurrency control, combines App and PAT credentials into unified slots, and updates card rendering to acquire and rotate tokens per retry attempt.

Changes

GitHub App token lifecycle

Layer / File(s) Summary
App token minting and caching
api/utils/github-app-token.ts, tests/utils/github-app-token.test.ts
Adds environment-based App configuration, private-key normalization, RS256 JWT creation, installation discovery, token minting, per-slot caching, refresh handling, concurrent mint coalescing, reset support, and lifecycle tests.

Unified token slots

Layer / File(s) Summary
App and PAT slot resolution
api/utils/github-token-updater.ts, tests/utils/github-token-updater.test.ts
Combines App installation slots with contiguous PAT slots, maps slot names, resolves tokens by index, and marks App acquisition failures for rotation.

Card retry integration

Layer / File(s) Summary
Per-attempt token rotation
api/utils/handle-card.ts, tests/utils/handle-card.test.ts
Acquires tokens inside the retry loop, rotates on token acquisition and rate-limit failures, and validates App-slot fallback and exhaustion behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant handleCard
  participant github-token-updater
  participant github-app-token
  participant GitHub
  handleCard->>github-token-updater: request token for current slot
  github-token-updater->>github-app-token: mint or reuse App token
  github-app-token->>GitHub: resolve installation and mint token
  GitHub-->>github-app-token: installation token
  github-app-token-->>github-token-updater: token or acquisition failure
  github-token-updater-->>handleCard: token or acquisition failure
  handleCard->>handleCard: rotate to next slot on failure
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: GitHub App installation tokens being added to the token pool.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/github-app-token

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/utils/github-app-token.ts`:
- Around line 71-81: Update appApi to include an explicit axios timeout in its
request configuration. Use the project’s established timeout constant or
configuration value if available, while preserving the existing method, URL, and
headers for installation discovery and token minting.

In `@tests/utils/handle-card.test.ts`:
- Around line 145-169: Update the `handleCard with a GitHub App slot` test setup
to declare the axios mock as a mutable `let` and instantiate `new
MockAdapter(axios)` inside `beforeEach()` before resetting it, ensuring each
test starts with an attached adapter. Keep `afterEach()` responsible for
restoring the per-test mock.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: eb7b579a-904f-4953-9178-68e58fe0aef4

📥 Commits

Reviewing files that changed from the base of the PR and between ecd8361 and bbda368.

📒 Files selected for processing (6)
  • api/utils/github-app-token.ts
  • api/utils/github-token-updater.ts
  • api/utils/handle-card.ts
  • tests/utils/github-app-token.test.ts
  • tests/utils/github-token-updater.test.ts
  • tests/utils/handle-card.test.ts

Comment thread api/utils/github-app-token.ts
Comment thread tests/utils/handle-card.test.ts
Review feedback: appApi now carries an explicit 10s axios timeout so a
hanging GitHub API call degrades to the PAT slots within the request
instead of riding the invocation to the platform kill. The handle-card
App-slot tests recreate the MockAdapter per test — restore() detaches
the adapter, so a suite-level instance left later tests unmocked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vn7n24fzkq

Copy link
Copy Markdown
Owner Author

Both review points addressed in 7207986: appApi now carries an explicit 10s timeout so a hanging mint degrades to the PAT slots within the request, and the App-slot tests recreate the MockAdapter per test (restore() detaches the adapter, so the suite-level instance left later tests unmocked — good catch).

@vn7n24fzkq
vn7n24fzkq merged commit bfcf1c4 into main Jul 29, 2026
5 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.

1 participant