# GitHub App vs OAuth App — Findings Summary & Implementation Estimate

**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

---

## The question

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?

## Recommendation

**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.

---

## Key findings

1. **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.

2. **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.

3. **`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.

4. **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.

5. **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`).

## Funnel evidence (Mixpanel Production, EU, trailing 90d)

| 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) |

- **BitBucket is also OAuth but converts far better** — two OAuth providers, very different drop-off → the loss is **GitHub-consent-screen-specific**, not a generic "repo step is hard" problem. Strongest data argument for the pilot.
- GitHub's loss is mostly **silent abandonment** at the consent screen (47 explicit failures vs 535 initiations).
- *Caveat:* funnels show GitHub is the outlier but can't prove the consent screen is the *cause*. The pilot is the experiment that confirms causation.

---

## Implementation estimate

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.

### Tier 1 — Parallel-support pilot (the recommended next step)

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 |

### Tier 2 — App-level webhooks + disconnect simplification

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.

### Tier 3 — Full migration / OAuth App retirement (only if the pilot proves out)

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.

### Bottom line

- **To get pilot data: ~3 weeks of one engineer** (Tier 1).
- **Pilot + webhook simplification: ~4–4.5 weeks** (Tier 1 + Tier 2).
- Full migration is a separate, later, much larger decision gated on the pilot's results.

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.

---

## Open questions to resolve before Tier 1

- **Org-heavy customer base?** If most connections are org-owned, the new approval step needs first-class UX or the pilot could *regress* the funnel. (Could shift the onboarding-views estimate up.)
- **Secrets handling:** where does the App private key live — `external_api_keys.yml`, a secrets manager? — and how is it injected per environment?
- **Multi-project-per-repo count** in production — drives the Tier 2 App-webhook dispatch design.

---

## Links

- Full research doc: `thoughts/shared/research/2026-06-26-github-app-vs-oauth-app.md` (branch `research-github-app-spike`, pushed to origin)
- Funnel A (Mixpanel EU): https://eu.mixpanel.com/project/3539816/view/4041060/app/insights#FfTndyHR8DtD
- Funnel B (Mixpanel EU): https://eu.mixpanel.com/project/3539816/view/4041060/app/insights#oMfERu8Nc1M2
