Date: 2026-06-30
Source: thoughts/shared/research/2026-06-26-github-app-vs-oauth-app.md (committed on branch research-github-app-spike)
Status: Research complete — no production code changed
Should DeployHQ switch GitHub repo connection from an OAuth App to a GitHub App to reduce the ~15–20% onboarding drop-off at the repo step and recurring repo-access support tickets?
Phased — GO for a parallel-support GitHub App pilot, NO-GO for a forced migration.
Stand up a GitHub App alongside the existing OAuth App, route new onboarding connections through it, leave every existing connection untouched. Don't force-migrate live connections; don't retire the OAuth App on a deadline.
The git clone does NOT use the OAuth token. Deployments authenticate to GitHub with an SSH deploy key (Project#public_key), not the user OAuth token. The token only drives API ops: repo listing at onboarding, deploy-key install, webhook install, reading releases/branches/files, posting deployment statuses. → Blast radius is onboarding + API surface, not the deploy engine. This is the linchpin that contains the risk.
All GitHub API access funnels through one class — lib/repository_sources/github.rb (15 call sites via one @api Octokit client). The token-vs-App switch is essentially one initialize change plus reworking the /user-based methods (authenticated?, projects, token_valid?) that don't exist for installation tokens.
identity is NOT involved. The whole GitHub OAuth flow lives in the deployhq app. This is a deployhq-only change — not gated on any other team/service.
App installation tokens expire (~1h); DeployHQ has no token-minting infra today. The existing refresh infra is a deliberate no-op for GitHub (refresh_github_token! returns false). Real JWT-signing + installation-token minting/caching is genuinely new infrastructure and the top reliability risk — a failed mint blocks all API features for that installation at once.
Scope win. Current OAuth scope is repo + read:org — the broad "full control of all your repositories" consent screen. A GitHub App lets us request a scoped, per-repo permission set instead (contents:read, deployments:write, administration:write, metadata:read).
| Provider | Initiated→Connected | started-connect→repo-created |
|---|---|---|
| GitHub | 84% (16% drop) | 75% (25% drop) |
| BitBucket | 99% (1% drop) | 87% (13% drop) |
| GitLab | 83% (17% drop) | 80% (20% drop) |
These are engineering estimates for one developer familiar with the codebase, derived from the blast radius and risks the spike documents. They assume the GitHub App registration/admin setup is available and exclude PM/design/review-cycle latency.
New connections via a GitHub App; existing connections untouched; measure funnel + tickets against the existing A/B baseline. Keeps per-repo webhooks and the deploy-key flow as-is.
| Workstream | Estimate | Notes |
|---|---|---|
| GitHub App registration + secrets (App ID, private key) wiring across envs | 0.5–1 d | Where the PEM lives is an open question (see below) |
| Installation-token minting + caching (JWT RS256 → installation token, keyed by installation_id, refresh on expiry) | 3–5 d | The genuinely new infra; the bulk of the work and top risk |
installation_id storage + migration (new column / sibling table) | 1 d | Lhm not needed (small table); standard migration |
github.rb rework — initialize branch (token vs App), authenticated?/token_valid? → installation health check, projects/repositories → installation_repositories | 2–3 d | The /user-based methods are the breakage points |
| Onboarding/connect flow + views (App install redirect, org-approval guidance) | 2–3 d | Org-approval UX is a new drop-off point — needs first-class handling |
| Tests (incl. cross-account + foreign-account coverage, installation-token failure modes) | 2–3 d | Per project testing standards |
| Buffer for the reliability risk (mint/refresh edge cases, token-expiry races) | 1–2 d | |
| Tier 1 total | ~2.5–3.5 weeks (≈12–18 dev-days) | One engineer, end-to-end |
Replace per-repo create_hook with one App webhook + an inbound (installation_id, repo) → project dispatcher (1-to-many; one repo can back multiple projects). Simpler connect/disconnect, but new inbound routing + signature verification.
Estimate: ~1–1.5 weeks (≈5–8 dev-days). Risk: webhook continuity during the swap — must not drop auto-deploys.
Prompt existing users to reinstall as an App; eventually deprecate the OAuth App. Forced re-auth, comms, support load, long tail of un-migrated users.
Estimate: Large — multiple weeks of eng plus a comms/support campaign and a long migration tail. Not scoped in detail; revisit only after Tier 1 data.
The estimate is dominated by one genuinely new piece of infrastructure (installation-token minting/caching) and one new UX risk (org-approval drop-off). Everything else is mechanical change inside one well-isolated class.
external_api_keys.yml, a secrets manager? — and how is it injected per environment?thoughts/shared/research/2026-06-26-github-app-vs-oauth-app.md (branch research-github-app-spike, pushed to origin)
comments (0)