Devboxes
Feature Catalog

Repository Connections

GitHub App installs, manual PAT hosts, and the one-connection-per-project bind.

An organization reaches repositories through two connection kinds: a remembered GitHub App installation (github.com) or a manual connection — a Git host plus an encrypted personal access token — for GitHub, GitLab, Gitea, or Forgejo. Both feed one repository-listing surface. A project binds to exactly one connection at import, re-checked for access at import time and again at dispatch; the GitHub App path mints a short-lived per-run token scoped to the single repository, while the manual path uses the stored PAT.

GitHub-side installation lifecycle changes flow back through signature-verified webhooks: uninstall and suspension flip the stored connection between active, revoked, and suspended. Dispatch against a non-active connection answers 422 with the machine-readable code REPO_CONNECTION_REVOKED before any run or task row exists.

How to test it

SuiteProves
pnpm vp run @firops/api#testgithub-app-installations.test.tsThe connect flow's session, nonce, expiry, and installation-selection gates over real HTTP
pnpm vp run @firops/api#testrepository-credentials.test.tsInstallation rows sync, invalidate, and re-point projects without breaking rows that active runs still hold
pnpm vp run @firops/api#testopencode-provider-credentials.integration.test.ts and the dispatch suitesDispatch validates the repository credential before enqueueing
pnpm vp run @firops/api#testwebhooks.github.integration.test.tsSigned lifecycle deliveries mutate connection state idempotently, forged signatures die with 401 and no state change, and dispatch is blocked and restored accordingly

Manual smoke: Manage → Credentials → Repository — connect the GitHub App, add a manual host, then import a repository in New project.

Decisions

  • An org can only remember an installation the connecting user controls — the connect flow checks the authorizing user's own installation list, so one org can never import an app-global installation that belongs to someone else.
  • The connect flow is stateful and single-use — each attempt is a server-side session with a hashed nonce and a 15-minute expiry that transitions exactly once; a replayed or forged callback dies on the session, not on heuristics.
  • One active manual connection per organization + provider + host — saving again replaces the stored token instead of accumulating duplicates, so "which token does this host use" always has one answer.
  • Rotation and deletion are refused while anything depends on the connection — a rotate that would break an attached project's access, or a delete while projects or active runs still reference the connection, answers 409 with the dependents; the guard runs atomically with the write.
  • A project stores the connection reference, never a token — runs resolve credentials through the connection at run time, so rotating a PAT never requires touching projects.
  • Prefer the GitHub App path where possible — a per-run token scoped to one repository with write access to exactly contents, pull requests, and issues beats a long-lived personal access token.
  • Installation webhook handlers are set-state on GitHub's numeric installation ids, never toggle — replayed or reordered deliveries converge on the same connection state, and account or repository renames do not change the installation identity.
  • Signature verification reads the raw delivery bytes before any parsing — a constant-time HMAC compare against the shared webhook secret; unsigned or forged deliveries answer 401 and change nothing.
  • Dispatch against a dead connection fails with a typed code, not a failed run422 REPO_CONNECTION_REVOKED at run creation with reinstall copy, so the UI can point at the fix instead of surfacing a mid-run credential error.

Next: Runner images — the container a run needs before it can be claimed.

Terms of ServicePrivacy Policy

On this page