GitHub App vs OAuth App — Findings & Implementation Estimate

0 comments0 reviews

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


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.


  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 classlib/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).

ProviderInitiated→Connectedstarted-connect→repo-created
GitHub84% (16% drop)75% (25% drop)
BitBucket99% (1% drop)87% (13% drop)
GitLab83% (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.

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.

WorkstreamEstimateNotes
GitHub App registration + secrets (App ID, private key) wiring across envs0.5–1 dWhere 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 dThe genuinely new infra; the bulk of the work and top risk
installation_id storage + migration (new column / sibling table)1 dLhm not needed (small table); standard migration
github.rb rework — initialize branch (token vs App), authenticated?/token_valid? → installation health check, projects/repositoriesinstallation_repositories2–3 dThe /user-based methods are the breakage points
Onboarding/connect flow + views (App install redirect, org-approval guidance)2–3 dOrg-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 dPer 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.


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