Skip to main content
Fabro supports two GitHub integration strategies:
  • token — the default for local and individual use. Fabro captures gh auth token during fabro install, stores it as GITHUB_TOKEN, and uses that token directly for repo access, pull requests, and sandbox GITHUB_TOKEN injection.
  • app — the team-oriented option. Fabro registers a GitHub App, uses installation tokens for repo access, enables browser OAuth, and supports webhook delivery when you configure a webhook strategy.
token changes GitHub integration auth only. It does not provide browser sign-in, so the embedded web UI is disabled when strategy = "token".

Strategy matrix

Capabilitytokenapp
CLI pull requestsYesYes
Private repo cloningYesYes
Sandbox GITHUB_TOKENDirect tokenScoped installation token
Browser sign-inNoYes
Web UI routesDisabledEnabled
WebhooksNoStrategy-dependent

GitHub App mode

The rest of this page describes the app strategy, which is required for browser auth and for any webhook delivery strategy.
FeatureHow it’s used
OAuth loginUsers sign in to the web UI with their GitHub account
Private repo cloningDaytona and Docker sandboxes clone private repositories using short-lived Installation Access Tokens
Checkpoint pushingAfter each workflow stage, Fabro pushes the run branch and metadata branch back to origin from inside the sandbox
Auto-PRWhen [run.pull_request] enabled = true in the run config, Fabro opens a PR from the agent’s working branch after a successful run
Auto-mergeWhen [run.pull_request] auto_merge = true, Fabro enables GitHub’s auto-merge on created PRs so they merge automatically once required checks pass
Sandbox GITHUB_TOKENWhen [run.integrations.github.permissions] are declared at any layer (workflow, project, or user settings), Fabro mints a scoped Installation Access Token and injects it as GITHUB_TOKEN in the sandbox

Setup

Prerequisites

  • A GitHub account (personal or organization)
  • Shell access to the host that runs the Fabro server
  • The Fabro web app running (cd apps/fabro-web && bun run dev) if you want browser sign-in after setup

Register the GitHub App with fabro install

Run the installer on the same machine that will run the Fabro server:
fabro install
When you choose the GitHub App strategy, the CLI opens GitHub with a pre-filled App Manifest containing:
PermissionLevelPurpose
ContentsWriteClone repos, push run branches and checkpoints
MetadataReadLook up repository installation status
Pull requestsWriteCreate and update PRs from workflows
ChecksWriteReport workflow status on commits
IssuesWriteCreate issues from workflows
EmailsReadRead verified email for OAuth login
The installer:
  1. Lets you choose where to register the app. If you have the gh CLI installed, Fabro detects your GitHub username and any organizations you administer and lets you pick. If gh is not available, the installer prompts for a GitHub token and uses it to look up your identity and organizations.
  2. Opens GitHub in your browser so you can review the manifest and click Create GitHub App.
  3. Receives GitHub’s temporary callback on localhost, exchanges it for permanent app credentials, and writes:
    • app_id, client_id, and slug to ~/.fabro/settings.toml
    • GITHUB_APP_CLIENT_SECRET, GITHUB_APP_WEBHOOK_SECRET, and GITHUB_APP_PRIVATE_KEY to the server vault
  4. Prints the resulting app slug so you can install the app on the repositories Fabro should access.
After setup completes, restart the Fabro server before attempting browser login.

Install the app on GitHub

Go to https://github.com/settings/apps/<your-app-slug>/installations and install it on the repositories Fabro should access.

Verify the configuration

Run the doctor command to check that all GitHub App credentials are in place:
fabro doctor
The GitHub App check verifies five fields:
FieldSource
server.integrations.github.app_id~/.fabro/settings.toml
server.integrations.github.client_id~/.fabro/settings.toml
GITHUB_APP_CLIENT_SECRETserver vault
GITHUB_APP_WEBHOOK_SECRETserver vault
GITHUB_APP_PRIVATE_KEYserver vault
If all five are set, the check passes. If none are set, it warns (GitHub integration is optional). If some are set but others are missing, it errors with the specific missing fields.

Configuration

The GitHub App configuration lives in two places:

~/.fabro/settings.toml

settings.toml
[server.integrations.github]
app_id = "123456"
client_id = "Iv1.abc123def"
slug = "fabro-a3f2"
FieldDescription
app_idNumeric GitHub App ID
client_idOAuth Client ID for the app
slugApp slug, used for linking to the GitHub App settings page

Server vault

Fabro stores the GitHub App secrets in the server vault under these keys:
  • GITHUB_APP_CLIENT_SECRET
  • GITHUB_APP_WEBHOOK_SECRET
  • GITHUB_APP_PRIVATE_KEY
The private key is stored as base64-encoded PEM. Fabro also accepts raw PEM format (starting with -----BEGIN). Install mode writes these automatically. If you rotate them manually, use fabro secret set on the server:
fabro secret set GITHUB_APP_CLIENT_SECRET <client-secret>
fabro secret set GITHUB_APP_WEBHOOK_SECRET <webhook-secret>
fabro secret set --type file GITHUB_APP_PRIVATE_KEY <private-key-pem-or-base64>

Webhook delivery strategies

Fabro receives GitHub webhooks on POST /api/v1/webhooks/github whenever GITHUB_APP_WEBHOOK_SECRET is configured. The webhook strategy controls how that route becomes reachable from GitHub:
settings.toml
[server.integrations.github]
strategy = "app"
app_id = "123456"
client_id = "Iv1.abc123def"
slug = "fabro-a3f2"

[server.api]
url = "https://fabro-api.example.com"

[server.integrations.github.webhooks]
strategy = "server_url"
  • server_url: recommended for production. Fabro assumes server.api.url is already publicly reachable and best-effort updates the GitHub App webhook URL to <server.api.url>/api/v1/webhooks/github each time fabro server start runs.
  • tailscale_funnel: opt-in for machines reachable through Tailscale but not through a stable public URL. Fabro runs tailscale funnel against the main server port and best-effort updates the GitHub App webhook URL to the resulting Funnel origin.
  • Unset strategy: Fabro still serves the webhook route if the secret is present, but it does not expose the route for you and does not mutate the GitHub App webhook URL.
tailscale_funnel has host-wide side effects: it changes Tailscale Funnel state on the server and rewrites the GitHub App webhook URL on startup. Use server_url when you already have HTTPS and a stable hostname.

Reconfigure GitHub after install

To switch GitHub strategies or re-register the app without re-running the full install wizard, use fabro install github:
fabro install github
This walks through just the GitHub setup steps — choosing a strategy, registering an app, or storing a token — and restarts the server when done. Stale settings and secrets from the previous strategy are cleaned up automatically.

token mode

Choose GitHub CLI in fabro install to use the default local-user flow. The installer:
  1. Runs gh auth token
  2. Stores the token as vault secret GITHUB_TOKEN
  3. Writes strategy = "token" under [server.integrations.github]
After install, Fabro reads GITHUB_TOKEN from the server vault only. To update it after install, run:
fabro secret set GITHUB_TOKEN <token>
Short-lived GitHub installation tokens (ghs_*) are rejected as static tokens; use a PAT for token mode, or use GitHub App mode so Fabro can refresh installation tokens for you. In this mode, Fabro disables the embedded web UI and browser auth routes. Machine API routes and /health continue to work.

How it works

OAuth login

The web app uses the GitHub App’s OAuth credentials to authenticate users:
  1. User clicks Sign in with GitHub on the login page
  2. Fabro redirects to GitHub’s OAuth authorization endpoint with scopes read:user and user:email
  3. User authorizes the app on GitHub
  4. GitHub redirects back with an authorization code
  5. Fabro exchanges the code for an access token and fetches the user’s profile and verified email
  6. Fabro checks the username against the allowed_usernames list in settings.toml
Configure allowed users in settings.toml:
settings.toml
[server.auth.github]
allowed_usernames = ["alice", "bob"]
An empty allowed_usernames list rejects all users.

Repository cloning in sandboxes

When a workflow runs in a remote sandbox (Daytona or Docker), Fabro clones the current repository into the sandbox using the GitHub App:
  1. Fabro detects the local repository’s origin remote URL and current branch
  2. SSH URLs (e.g. git@github.com:owner/repo.git) are converted to HTTPS
  3. Fabro signs a short-lived JWT using the App ID and private key (RS256, 10-minute validity)
  4. Using the JWT, Fabro looks up the GitHub App installation for the repository (GET /repos/\{owner\}/\{repo\}/installation)
  5. Fabro requests a scoped Installation Access Token with contents: write permission on the specific repository
  6. The sandbox clones via HTTPS using x-access-token as the username and the token as the password
For public repositories, the clone works without credentials. The token is still generated because it’s needed for pushing checkpoints.

GITHUB_TOKEN injection

When any settings layer declares [run.integrations.github.permissions], Fabro prepares a scoped GitHub App token source and exposes it as the GITHUB_TOKEN environment variable in sandbox command and agent execution. Agents running inside the sandbox can use this token for GitHub API calls, cloning additional private repos, or pushing to branches. The GitHub CLI (gh) reads GITHUB_TOKEN automatically, so command stages can run gh pr list, gh issue create, and similar commands without an explicit gh auth login.
workflow.toml
[run.integrations.github.permissions]
contents = "write"
pull_requests = "write"
Only the listed permissions are requested — the token is scoped to the minimum access needed. If the GitHub App isn’t configured or the repository lacks an installation, the run logs a warning and continues without the token. Installation Access Tokens are short-lived, so Fabro refreshes them when they are close to expiry. Command stages and API-mode agent stages resolve GITHUB_TOKEN before use, which keeps long workflows working across token rollover. CLI-mode agent stages receive their token at launch time; for very long CLI-agent stages, run GitHub operations through command stages or API-mode agents if mid-stage token refresh matters. The permissions table follows the standard layer-merge order (workflow > project > user > defaults). Set defaults at [run.integrations.github.permissions] in ~/.fabro/settings.toml so every run inherits a baseline; tighten or override per-workflow as needed. A higher layer that defines permissions = {} clears the inherited map (no token requested).

Security model

The upper bound on what Fabro will mint is whatever permissions the GitHub App installation has been granted. Fabro does not impose a separate server-side cap on the run-level permissions map: any value the App has been granted can be requested by run config. Operators must not run untrusted workflow, project, or user TOML against a broadly-scoped GitHub App installation. Preflight prints the resolved permission set so reviewers can see what each run will request.

Checkpoint pushing

After each workflow stage, Fabro checkpoints by pushing the run branch and metadata branch to origin. Inside remote sandboxes, the git remote URL is configured with the Installation Access Token for authenticated pushing. For long-running workflows, Fabro refreshes the token before each push since Installation Access Tokens are short-lived (typically 1 hour).

Troubleshooting

”GitHub App is not installed for {owner}”

The GitHub App exists but hasn’t been installed on the organization or user account that owns the repository. Install it at:
https://github.com/organizations/{owner}/settings/installations
Or for personal accounts:
https://github.com/settings/installations

“GitHub App is private but this repo belongs to a different owner”

GitHub Apps are private by default, meaning they can only be installed on repositories owned by the same account or organization that owns the app. If you need to install the app on a repository owned by a different user or organization, you must make the app public:
  1. Go to GitHub → Settings → Developer settings → GitHub Apps → {your app}
  2. Scroll to the bottom under Danger zone
  3. Click Make public
Once the app is public, anyone with the installation link can install it on their repositories. This does not grant the app any additional permissions — repository owners still choose which repositories the app can access during installation.

”GitHub App installation is suspended”

The installation was disabled in GitHub’s settings. Re-enable it in the organization’s GitHub App settings.

”GitHub App does not have access to repository {repo}”

The app is installed but doesn’t have access to this specific repository. Update the installation’s repository permissions to include it (the app may be configured for “Only select repositories”).

”GitHub App authentication failed”

The app_id in settings.toml or the GITHUB_APP_PRIVATE_KEY vault secret is incorrect. Re-run fabro install on the server host or verify the values match your GitHub App.

Clone fails for private repositories

If you see Git clone failed ... If this is a private repository, configure a GitHub App, the GitHub App credentials are not configured. Run fabro install on the server host or verify with fabro doctor.