Skip to content

Setting up the GitHub App

Factoryal reaches GitHub through a GitHub App. The App's private key stays in the control plane (the API). Coding agents get 1-hour tokens that can push to one repository, and pull requests are opened by the platform with a separate token, so they show up as authored by the App's bot.

1. Register the App

GitHub → Settings (your org's settings, for org repos) → Developer settings → GitHub Apps → New GitHub App.

Field Value
GitHub App name e.g. acme-factoryal (the bot shows as acme-factoryal[bot])
Homepage URL anything, e.g. your Factoryal URL
Webhook → Active ✅ (untick it if you don't have the URL yet, e.g. before the first AWS deploy)
Webhook URL https://<your-factoryal-api>/v1/integrations/github/webhook
Webhook secret a long random string you choose (openssl rand -hex 32); you'll set it as FACTORYAL_GITHUB_WEBHOOK_SECRET

Deploying to AWS for the first time? The webhook URL is a stack output, so it doesn't exist yet. Create the App with the webhook inactive, deploy, then set the URL and secret and tick Active (see deploy-aws.md, step 4).

Repository permissions (nothing else):

Permission Access Why
Contents Read and write Agents push branches; webhooks read repo files to refresh standards facts; the dispatcher reads factory repos
Pull requests Read and write The platform opens PRs, reads reviews, and comments on its PRs and on factory repo PRs (the plan)
Commit statuses Read and write factoryal/apply and factoryal/plan statuses on factory repo commits
Checks Read-only Receive CI results (check_suite events)
Metadata Read-only Required by GitHub

Subscribe to events: Push, Pull request, Check suite, Pull request review, Pull request review comment, Pull request review thread. A push to a factory repo's branch applies it, and a pull request against it gets a plan (see factory.md). The review event lets a review that asks for changes on a Factoryal PR start a revision (see agents.md); the comment one lets the reviewer answer people who reply to its inline comments (below); the thread one tells findings when a comment's thread is resolved.

Already created the App? Add Commit statuses: Read and write on its page (Permissions & events). GitHub then asks each installation to accept the new permission.

Where can this GitHub App be installed? "Only on this account".

Click Create GitHub App. Note the App ID.

2. Create a private key

On the App's page → Private keys → Generate a private key. A .pem file downloads. Store it as a secret: locally in ./secrets/github-app.pem (git-ignored), on AWS in the factoryal/github secret, readable only by the API (see deploy-aws.md, step 2).

3. Install it

App page → Install App → choose your org → Only select repositories → pick the repos Factoryal may work on. Repos you don't select stay out of reach even if an agent's scopes name them: the token mint fails with "the GitHub App is not installed on …".

4. Configure Factoryal (API and dispatcher)

FACTORYAL_GITHUB_APP_ID=123456
FACTORYAL_GITHUB_PRIVATE_KEY_FILE=/secrets/github-app.pem   # or FACTORYAL_GITHUB_PRIVATE_KEY=<pem contents>
FACTORYAL_GITHUB_WEBHOOK_SECRET=<the webhook secret>

Workers need only ANTHROPIC_API_KEY (with Managed Agents access) and the Factoryal API URL and token.

5. Put the repos in the catalog

A repo is reachable by its url property:

kind: Entity
blueprint: repo
identifier: billing-api
team: payments
properties:
  url: https://github.com/acme/billing-api
  default_branch: main

Give a coding agent repo:write:billing-api (one repo) or repo:write (any repo in the catalog), then use it in a code step. See examples/demo/projects/delivery/workflows/linear-agent-fix.yaml.

6. Check it

  • On the App page → Advanced → Recent deliveries, the initial ping should show a 202 response from Factoryal.
  • Push to the default branch of an installed repo: its has_readme, has_ci, ... properties update.

7. Review pull requests

examples/demo/projects/code-review/workflows/pr-review.yaml reviews PRs the way CodeRabbit does. On every push to an open, non-draft PR, it posts one review with a summary, a walkthrough table of the changed files, and inline comments. Comments can carry a suggestion block that GitHub can apply in one click.

It needs:

  • The pull_request blueprint from examples/demo/catalog/catalog.yaml. With it, pull_request webhooks keep a PR entity per PR (number, state, draft, head_sha, ...). Only the properties the blueprint defines are written.
  • The repo in the catalog (step 5). PRs on repos that aren't in the catalog are ignored.

A push changes the entity's head_sha, and the workflow's entity_event trigger starts a review on that change. Edits to the title or description don't start one. The workflow reads the diff with a github_pull step, which puts the new file's line number in front of each diff line so the model can point comments at them, and leaves out the generated files its exclude patterns match (listed as skipped). Its text output is the PR ready for the prompt: title, description, the App's earlier comments and the numbered diff. Then it posts with a notify step with target: github_review and pull: pr. Change the system prompt to set what the reviewer looks for.

Re-reviews don't repeat themselves. since: last_review reads only what changed since the commit of the App's latest review on the PR, as GitHub records it, so a failed review, which posts nothing, has its changes come back in the next one. The whole diff is read on a first review, or when that commit is gone (force-push). text lists the App's earlier inline comments as [kind] text with their fix as Proposed: ..., without the headers and suggestion blocks the review added, and the prompt tells the model not to repeat them.

The file also has pr-review-reply, which answers when someone replies to the reviewer's inline comments. It needs the review_reply blueprint from examples/demo/catalog/catalog.yaml: with it, pull_request_review_comment webhooks record replies to the App's own comments as entities (replies to anyone else, or by the App itself, are ignored). There is one entity per GitHub review, so replies submitted together ("Start a review", or several threads at once) start a single run; the other webhooks of that review find it recorded and start nothing. The run reads the PR with github_pull and review: <id>, whose threads output holds the App's threads that review replied in and that the App hasn't answered since. The model answers each one from threads_text, or leaves a reply empty when it needs no answer (an acknowledgment like "done" or "thanks"), and one notify step with target: github_reply, pull: pr and replies posts the rest, each in its own thread.

To review a PR by hand: Workflows → pr-review → repo: billing-api, number: 12. To answer one thread by hand: Workflows → pr-review-reply → repo: billing-api, number: 12, and review: <review id> (or thread: <comment id> for one thread).