Runs the hub (apps/hub, :3000) and portal (apps/web, :5173) against a local Postgres. The other way to run it is in the README. The portal sets everything else up after you sign in. Running it for real is in SELF_HOST.md.
- Bun 1.4+ (CI runs 1.4.2)
- Postgres 15+ (Homebrew or Docker)
- A GitHub account that can create GitHub Apps and own a sandbox repository
bun installCreate an empty database whose role has a password: the migration tool refuses a DATABASE_URL without one (Please provide required params for Postgres driver: [x] password: '').
docker run -d --name corbits-postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=interchange -p 5432:5432 pgvector/pgvector:pg16
# or, on a Homebrew Postgres:
createdb interchange
psql -d interchange -c "create role postgres login superuser password 'postgres'"Either gives the DATABASE_URL in .env.example, postgres://postgres:postgres@localhost:5432/interchange, which the hub tests also use.
cp .env.example .envSet the four secrets to different openssl rand -hex 32 values and set DATABASE_URL to the database from step 2; leave the rest as it is. Run everything from the repo root so both services inherit the root .env.
bun run devIt first applies the database migrations (bun run db:migrate), then starts both services; the hub itself never migrates.
Hub on http://localhost:3000 (API only), portal on http://localhost:5173 (it proxies /api to the hub and bundles the workflows on start). Restart after changing apps/hub or packages/; only the portal hot-reloads.
Sign up at http://localhost:5173; the portal shows Set up Triage with three steps: Connect a source, Repositories, Decision model. Follow Set up in the portal, using a sandbox repository. GitHub App credentials and the model key go to the hub vault, not .env.
The App's webhook URL is built from BETTER_AUTH_BASE_URL, and GitHub refuses a manifest whose webhook URL it cannot reach, so a hub on localhost registers the App without a webhook and finds pull requests by polling. For live PR events, forward a tunnel or a smee channel to http://localhost:3000/api/hooks/<tenant-id>/github-hook (the URL shown under Connect → Connect an existing GitHub App instead), then in the App's settings on GitHub activate the webhook with the tunnel's public URL and a secret of your choosing, and save that secret in the portal through the same form with the App ID and a private key downloaded from the App's settings.
emulate.config.yaml seeds org acme, repo acme/widgets, user octocat (token octocat_token), and App 12345 installed on acme/widgets. The emulator sends signed pull_request, review and comment webhooks to the App's webhook_url; it does not send installation events.
-
Start the stack against it:
GITHUB_API_ORIGIN=http://localhost:4000 VITE_GITHUB_API_ORIGIN=http://localhost:4000 bun run dev. -
Create an account and copy the webhook URL shown under Connect → Connect an existing GitHub App instead.
-
Uncomment
webhook_urlinemulate.config.yamlwith that URL, then start the emulator:npx emulate start --service github --generated-secrets-file .emulate-secrets.json. -
In Connect, enter App ID
12345, the PEM from.emulate-secrets.json, and webhook secretemulate-webhook-secret. Then add a decision model (Add my own provider, any HTTPS endpoint). -
Register the repository by sending the installation event GitHub would send:
BODY='{"action":"added","installation":{"id":100,"account":{"login":"acme"}},"repository_selection":"selected","repositories_added":[{"full_name":"acme/widgets"}],"repositories_removed":[]}' SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac emulate-webhook-secret | sed 's/^.* //') curl -X POST "<webhook-url>" -H 'content-type: application/json' -H 'x-github-event: installation_repositories' \ -H "x-github-delivery: $(date +%s)" -H "x-hub-signature-256: sha256=$SIG" -d "$BODY"
-
Turn on triage for
acme/widgets, then open a pull request asoctocatthroughhttp://localhost:4000(Authorization: Bearer octocat_token). The emulator delivers the webhook and triage runs.
Restarting the emulator regenerates the App key; reconnect the App after each restart or pin private_key in the config.
Code review against the emulator: the emulator ignores GitHub's diff media type and answers a pull request's JSON, which the hub refuses as no diff. Point GITHUB_API_ORIGIN at a small proxy in front of the emulator that answers GET /repos/{owner}/{repo}/pulls/{number} with 406 when it asks for a diff, as GitHub does for a very large pull request; the hub then builds the diff from the files' patches. Save an Anthropic key in Settings and switch code review on for acme/widgets in its repository panel. A review calls Anthropic for every step; to try the wiring without spending, point the anthropic catalog provider's baseURL at a stand-in that answers /v1/messages, and deploy before the portal next reconciles the catalog.
bun run test # unit + integration; hub tests need Postgres
bun run check # typecheck web, tooling, extension, hub; bundle the hub
bun run lintHub tests connect to postgres://postgres:postgres@localhost:5432/interchange and ignore DATABASE_URL.
Scripts are listed in IMPLEMENTATION.md.
packages/policy-check-action/src/cli.ts check [--base <ref>], run inside any clone, runs the base branch's rules that need no model on the working tree's change through the pr-triage definition, reading only local git (README).
- Hub exits on boot naming a secret: one of the four keys is missing or not 64 hex characters.
- "The workspace has no decision model": store a decision model key in the Triage section of Settings.
- "Could not deploy triage workflows": the notice carries the hub's reason. Reload to retry.
- New PRs never reach triage: the hub logs
stale_deploymentwhen the tenant has no live pr-triage deployment for the repository's host (pr-triage-githuborpr-triage-bitbucket-datacenter). Reload the portal, or wait about two minutes for the hub to redeploy it. - Portal shows
HTTP 404from a hub route: the hub process is older than the code. Restartbun run dev.