Self-host Mergepatrol
Run it
GitHub webhooks wake a queue as soon as something changes, but GitHub only delivers them to a public https URL. On localhost, Mergepatrol polls.
-
Generate the secret key. It encrypts the GitHub App and the model API keys in the database. Keep it: without it, you must set Mergepatrol up again.
export MERGEPATROL_SECRET_KEY=$(openssl rand -base64 32) -
Start Mergepatrol:
docker compose up --build -d docker compose logs mergepatrol | grep setup_tokenOr without Docker for Mergepatrol itself, with Rust (
rust-toolchain.tomlpicks the version) and Node 24 with pnpm:docker compose -f compose.dev.yml up -d # or any Postgres 17 (cd ui && pnpm install && pnpm build) # the dashboard, which the binary embeds export MERGEPATROL_DATABASE_URL=postgres://mergepatrol:mergepatrol@127.0.0.1:54329/mergepatrol cargo run -p mergepatrol # prints the setup tokenJobs then run as local processes, without a sandbox. If port 8080 is taken, set
MERGEPATROL_BIND=127.0.0.1:<port>andMERGEPATROL_PUBLIC_URL=http://localhost:<port>. -
Open http://localhost:8080 and do the setup in the dashboard:
- Enter the setup token from the logs.
- Create the GitHub App. GitHub creates it with the permissions Mergepatrol needs. If the button does nothing, the name is taken, even by an App you deleted: change it.
- Install the App on your organization or account, and choose its repos. GitHub sends you back to sign in.
- In Repos, set up the repos to queue. Each gets a PR that adds its config file: see Repo settings. Merge it, and the repo queues.
- In Settings, add your Anthropic or OpenAI API key. Mergepatrol checks it with the provider, then stores it encrypted. Create a key for Mergepatrol alone, with a spend limit (for Anthropic, in a workspace with one), so a leaked key costs at most the limit. For OpenAI, you can Sign in with ChatGPT instead, and Codex runs on your ChatGPT plan. Mergepatrol signs in the way the Codex CLI does, which OpenAI does not support for other apps and could stop at any time: keep an API key to switch back to.
-
Add the
mergepatrollabel to a PR to queue it. Mergepatrol creates the queue labels on each repo it queues.
Anyone with a GitHub account can sign in. Each installation of the App is a workspace, with its own repos and its own model API keys. A user sees the workspaces that GitHub lets them access. A session reads them from GitHub again every hour, so a new installation shows up, and a lost one goes, within the hour. A session lasts 30 days from its last use, and ends when GitHub stops taking the user’s token: when they revoke it, or after 6 months, when GitHub’s refresh token expires.
Environment variables
If you reach Mergepatrol at another address than http://localhost:8080, set MERGEPATROL_PUBLIC_URL before you create the App: GitHub sends users back to it, and delivers webhooks there when it is https. To get webhooks on your machine, run a tunnel such as cloudflared tunnel --url http://localhost:8080 and use its URL.
| Variable | Default | What it does |
|---|---|---|
MERGEPATROL_DATABASE_URL | (required) | Postgres. Compose runs one |
MERGEPATROL_SECRET_KEY | (required) | Encrypts the stored secrets. 32 bytes, base64. To rotate it, see Rotate the secret key |
MERGEPATROL_SECRET_KEY_FILE | A file that holds the secret key, instead of MERGEPATROL_SECRET_KEY. Compose uses one | |
MERGEPATROL_PUBLIC_URL | http://localhost:8080 | Where users reach the dashboard |
MERGEPATROL_LABEL | mergepatrol | The default trigger label. <label>:priority jumps the queue, and ejected PRs get <label>:failed |
MERGEPATROL_POLL_SECS | 60 | How often to poll GitHub, in case a webhook is lost |
MERGEPATROL_BATCH_SIZE | 8 | The default batch size: the most PRs tested together. 1 merges one PR at a time |
MERGEPATROL_MAX_JOBS | 4 | The most rebase and fix jobs that run at once, across every queue |
MERGEPATROL_DATA_DIR | ./data | The repo caches and the job outputs, with their transcripts. A job’s output is kept 7 days |
MERGEPATROL_BIND | 127.0.0.1:8080 | The dashboard address |
MERGEPATROL_PROVIDER | anthropic | The resolver’s default provider: anthropic or openai |
MERGEPATROL_MODEL | claude-opus-5-5 | The resolver’s default model. Required when the default provider is not anthropic |
MERGEPATROL_EFFORT | medium | The resolver’s default effort |
Run it with an App you made yourself
The hosted Mergepatrol uses one public GitHub App, which we create by hand on GitHub, rather than with the setup in the dashboard. A self-hosted Mergepatrol can do the same. Set the App’s credentials, and the setup token and the App creation are skipped:
| Variable | What it is |
|---|---|
MERGEPATROL_GITHUB_APP_ID | The App ID |
MERGEPATROL_GITHUB_APP_PRIVATE_KEY | The private key, PEM |
MERGEPATROL_GITHUB_CLIENT_ID | The OAuth client ID |
MERGEPATROL_GITHUB_CLIENT_SECRET | The OAuth client secret |
MERGEPATROL_GITHUB_WEBHOOK_SECRET | The webhook secret. Optional: without it, Mergepatrol only polls |
The App needs the permissions in crates/github/src/setup.rs (manifest), the callback URL <public URL>/auth/callback, the setup URL <public URL>/setup/installed, and the webhook URL <public URL>/webhooks/github, subscribed to the events in WEBHOOK_EVENTS in the same file.
Rotate the secret key
Generate a new key, and set it in front of the old one, separated by a comma: MERGEPATROL_SECRET_KEY=<new>,<old>. Restart Mergepatrol. At startup, it re-encrypts every stored secret with the new key, and logs how many. Then set only the new key, and restart again. Each session’s GitHub token is re-encrypted at its next hourly check; a session idle through the whole rotation signs out.
On GCP, a deploy runs the old revision beside the new one for a while, and each must read what the other writes. Add each step as a version of the mergepatrol-secret-key secret, and deploy it:
<old>,<new>: every revision can read both keys, and still encrypts with the old one.<new>,<old>: the new revision re-encrypts with the new key.<new>,<old>again, once the revision from step 1 has stopped: it re-encrypts what that one wrote with the old key meanwhile.<new>.
How jobs are isolated
A rebase job runs the repo’s code (the verify commands) and an agent with a shell. Mergepatrol treats it as untrusted:
- The orchestrator holds the GitHub App key and the queue. It never runs repo code.
- Each job runs
mergepatrol jobin its own container, from the job image. It gets a GitHub token that can only read the one repo, and revokes it once it has fetched the repo. It returns a git bundle. - The workspace’s model API keys and ChatGPT login stay in the orchestrator, which renews the login. A job calls its provider’s API through the orchestrator’s proxy, at
/anthropicfor Claude Code or/openaifor Codex, with a token that works only while the job runs, and for a set number of requests. - The orchestrator checks the bundle before it publishes: the result must sit on the base branch, and change no file that the PR did not change.
In production the job containers use gVisor (runsc) on a network that cannot reach the cloud metadata server, private ranges, or the host. deploy/sandbox-host.sh sets that up on a Linux VM. Then set:
| Variable | Default | What it does |
|---|---|---|
MERGEPATROL_EXECUTOR | local | docker runs each job in a container. agent-sandbox runs it in a GKE sandbox: see On GCP. local runs it as a process, without isolation, for development |
MERGEPATROL_JOB_IMAGE | mergepatrol-job:latest | The job image (docker build --target job) |
MERGEPATROL_JOB_RUNTIME | Docker’s default | runsc for gVisor |
MERGEPATROL_JOB_NETWORK | Docker’s default | mergepatrol-jobs, from deploy/sandbox-host.sh |
MERGEPATROL_JOB_RESOLV_CONF | Docker’s default | A file on the Docker host, mounted as the jobs’ /etc/resolv.conf. /etc/mergepatrol/job-resolv.conf on the sandbox network |
MERGEPATROL_JOB_PROXY_URL | MERGEPATROL_PUBLIC_URL | Where jobs reach the orchestrator: its model API proxy, and a GKE sandbox’s link. http://10.213.0.1:8081 on the sandbox network |
MERGEPATROL_JOB_PROXY_BIND | None | A second listener that serves only what jobs use, for jobs that cannot reach the dashboard’s. 0.0.0.0:8081 in compose.yml |
MERGEPATROL_JOB_MEMORY | 4g | Memory limit per job |
MERGEPATROL_JOB_CPUS | 2 | CPU limit per job |
The job image has git, the Claude and Codex CLIs, Rust, Go, Python, and Node with pnpm, for the verify commands.
Each release publishes both images, tagged with its version and latest: ghcr.io/agent-labs-dev/mergepatrol, the orchestrator, and ghcr.io/agent-labs-dev/mergepatrol-job. To run a release instead of building, set MERGEPATROL_JOB_IMAGE=ghcr.io/agent-labs-dev/mergepatrol-job:<version>.
Docs
- Get started with Mergepatrol
Sign in with GitHub, install the App, merge the setup PR, add a model key, and label a pull request to queue it.
- How the merge queue works
What a queued PR needs, the agent that resolves conflicts and fixes failed checks, automatic review, branches, batches, stacks and PRs from forks.
- Configuration: .mergepatrol/mergepatrol.yml
Every setting in a repo's .mergepatrol/mergepatrol.yml: the label, batch size, the agent's model, verify commands, CI fixes, extra branches and review bars.
- Pricing
Mergepatrol is free on public repos. Private repos cost $15 a seat a month after a 14-day trial with no card. Self-hosting is free.
- MCP server and API
Let Claude Code, Codex or Cursor queue and review PRs through Mergepatrol's MCP server, or read a PR's queue state from the JSON API.