Import from GitHub: the GitHub App

Goal: pick repositories to import straight from GitHub, and build private github.com repositories on your runners, without deploy keys or personal-access tokens on the machines that build.

saggar.dev runs a GitHub App. It is separate from login: signing in with GitHub stays the plain OAuth provider (see Sign the CLI in). The App gives the account three things:

  • the repo listing the import page offers,

  • private-repo clones: the service mints a short-lived installation token when a runner claims the job, so the runner clones what its own credentials could never reach,

  • the app webhook, which keeps installations current.

1. Install the App on your repositories

Install the App on the repositories you want to import: your own account, or an org you administer. Adding repositories to the installation, or removing them, updates the listing; uninstalling detaches it.

Installations attach to accounts by the numeric GitHub account id their linked github identity already stores (logins rename; ids do not). A user installation matches that user’s account directly. An org installation matches no account, so the installing user (whoever clicked Install) stands in. An installation whose account matches no account still serves clone credentials; it just lists for no one.

2. Import from the listing

With the App installed, the import page’s GitHub provider reads the caller’s own installations:

$ curl -H "X-Auth-Token: sgt-…" \
    https://saggar.dev/api/v1/forges/github/repositories

For each repository: the clone URL, full name, description and last update. The listing is the caller’s own installations only; no standing rules are invented. Nothing to list answers an empty list plus a reason, rendered as-is, so the UI can show why nothing is listed.

3. Private clones on your runners

A job cloning from github.com/<owner>/<repo> covered by an installation is rewritten at claim time: the claim response’s source URL carries a freshly minted installation token (https://x-access-token:…@github.com/…). The runner needs no GitHub credential at all. The token is valid for seconds, appears in that one response only, and is never stored or logged; GitHub expires it after an hour regardless. Public repositories keep their plain clone URL.

A repo with a matching watch builds on every push as any other (see Set up CI: watched repos, webhooks, statuses). Polling is plain git ls-remote, so it only sees the refs the URL exposes without a credential; the claim-time token takes the build’s own clone the rest of the way, and a repository webhook brings private pushes in instantly (see Set up CI: watched repos, webhooks, statuses).