The question I keep seeing on GitHub Discussions: “Can OpenClaw automatically review and merge my pull requests?” Yes, and it takes less than an afternoon to wire up. This post shows the exact setup I run on a few internal repos — GitHub App creation, webhook wiring, OpenClaw config, review prompts, approval rules, and a guarded auto-merge that will never push straight to main unless you explicitly allow it.
Architecture overview: GitHub App ➜ Webhook ➜ OpenClaw Gateway
At a high level:
- A GitHub App emits
pull_requestandcheck_runevents to a webhook URL. - The OpenClaw Gateway (Node 22+,
@openclaw/core 0.37.2) listens on/api/githuband translates those events into tasks. - An agent picks up the task, runs a code-review prompt against the diff, posts a comment, and (optionally) approves & triggers a merge via the GitHub REST API.
The entire flow is stateless on the cloud side; secrets stay in your GitHub App and in gateway.config.js. No need for a long-lived bot token with repo write access.
Creating a GitHub App for OpenClaw
- Navigate to Settings → Developer settings → GitHub Apps → New GitHub App.
- App name:
openclaw-pr-bot(any name is fine). - Webhook URL:
https://YOUR_GATEWAY_HOST/api/github. Set the content type toapplication/json. - Webhook secret: generate a random string, e.g.
pwgen 32 1. We’ll reference it asGITHUB_WEBHOOK_SECRET. - Permissions (the minimum that works today, 2024-06-09):
- Contents → Read-only (for diff fetching)
- Pull requests → Read & Write (to comment, approve, merge)
- Checks → Read-only (so the agent can see CI status)
- Metadata → Read-only (required by the API)
- Subscribe to events: enable
Pull requestandCheck run. - Create the app, then on the app page generate a Private key and download
openclaw-pr-bot.pem. - Install the app on the repository (or org) you want OpenClaw to touch.
If you have security compliance requirements, create a second installation for staging vs production; the same app ID works with multiple installations.
Storing GitHub credentials in OpenClaw
The gateway needs three pieces of data:
APP_ID– visible on the GitHub App pageINSTALLATION_ID– found under “Installations” → click repo → copy ID from the URLPRIVATE_KEY– the PEM we downloaded
On self-hosted OpenClaw the simplest path is environment variables:
export GITHUB_APP_ID=428903
export GITHUB_INSTALLATION_ID=39485723
export GITHUB_PRIVATE_KEY="$(cat openclaw-pr-bot.pem | awk '{printf "%s\\n", $0}')"
export GITHUB_WEBHOOK_SECRET=paZxL5OqKa82ZtgI2RZ4RPdX6aGLqYkD
If you are on ClawCloud set these in the “Secrets” tab of your agent dashboard; the build pipeline injects them into the container.
Installing the GitHub plugin
The gateway ships minimal; GitHub support lives in @openclaw/plugin-github. Install and wire:
npm i @openclaw/plugin-github@0.11.0 --save
In gateway.config.js:
import { githubPlugin } from "@openclaw/plugin-github";
export default {
port: 3000,
plugins: [
githubPlugin({
appId: process.env.GITHUB_APP_ID,
installationId: process.env.GITHUB_INSTALLATION_ID,
privateKey: process.env.GITHUB_PRIVATE_KEY,
webhookSecret: process.env.GITHUB_WEBHOOK_SECRET,
autoMergeLabel: "automerge", // Only merge PRs carrying this label
protectedBranches: ["main", "stable"], // Never push directly
})
]
};
Restart the gateway:
npm run openclaw:gateway
Configuring the pull_request webhook endpoint
No extra work on the GitHub side; we already pointed the GitHub App’s webhook at /api/github. Internally the plugin routes events:
- pull_request.opened, .synchronize –> enqueue
review-pr - pull_request.labeled –> if label ==
automergeenqueuetry-merge - check_run.completed –> re-enqueue
try-mergeso we merge only when CI passes
If you prefer GitHub webhooks over the App model you can still do it, but you’ll need a PAT with repo write scope — I avoid that because it’s harder to rotate.
Writing the code-review prompt
OpenClaw doesn’t ship with a one-size-fits-all prompt. Store yours under prompts/review-pr.md (or supply inline). My trimmed version:
You are an opinionated senior engineer. Review the diff below.
Rules:
- Focus on architecture & security, not styling.
- If something is critical, prefix with "BLOCKER:".
- If everything looks good, respond with "LGTM" only.
Diff:
{{diff}}
Reference it in gateway.config.js:
githubPlugin({
...,
reviewPrompt: fs.readFileSync("prompts/review-pr.md", "utf8"),
model: "gpt-4o-mini", // any provider supported by your agent
})
For large diffs the plugin automatically chunks 10k tokens at a time and streams partial comments.
Implementing the review task
The plugin offers defaults, but you can override the agent code if you need fully custom logic:
export const tasks = {
"review-pr": async ({ github, payload, tools }) => {
const { owner, repo, number } = payload;
const diff = await github.getPullRequestDiff(owner, repo, number);
const review = await tools.llm.complete({
prompt: tools.prompt("review-pr", { diff })
});
// Post a PR comment
await github.createComment(owner, repo, number, review);
// Approve automatically if the model said LGTM with no block
if (/^LGTM\s*$/m.test(review)) {
await github.approvePullRequest(owner, repo, number);
}
},
"try-merge": async ({ github, payload }) => {
const { owner, repo, number } = payload;
const pr = await github.getPullRequest(owner, repo, number);
const ciOk = pr.statuses.every(s => s.state === "success");
const hasLabel = pr.labels.some(l => l.name === "automerge");
const notProtected = !["main", "stable"].includes(pr.base.ref);
if (ciOk && hasLabel && notProtected) {
await github.mergePullRequest(owner, repo, number, {
merge_method: "squash"
});
}
}
};
That’s roughly 60 lines; maintaining it locally is easier than waiting for an upstream change if your policy evolves.
Testing the flow locally
- Fire up
ngrokso GitHub can reach your laptop:ngrok http 3000 - Update the GitHub App’s Webhook URL to the ngrok HTTPS endpoint.
- Create a dummy PR targeting a non-protected branch (
dev/test-automerge). - Add the
automergelabel. - Push a commit that passes CI (or stub the check with
workflow_run). - Watch
npm run openclaw:gateway --verbose. You should see logs:[github] Received pull_request.synchronize for #42 [agent] Enqueued task review-pr #42 [agent] LLM response: LGTM [github] Approved PR #42 [agent] Enqueued task try-merge #42 [github] Merged PR #42 via squash
No merge? Check:
- CI red → the agent will retry when a new
check_runhits. - Label missing → add
automerge. - Target branch protected → expected; push a release branch instead.
Hardening the setup: scoping permissions & branch protection
Blind auto-merge straight into main is how you end up on r/githubmemes. A few guards I run in production:
- Branch protection rules. Require one human review on
main; OpenClaw’s approval alone isn’t sufficient. Release branches (release/*) can be auto-merged because they’re cut from CI-green commits anyway. - Scoped GitHub App. Install the app on specific repos, not the entire org. Even if the private key leaks, damage radius is contained.
- Rate limits. Add
x-openclaw-retryheaders so the gateway stops after 5 failed merge attempts in 24 h. Prevents spam. - Audit logs. GitHub keeps detailed logs for app actions. Search for
"openclaw-pr-bot" action:mergeif something looks fishy. - Per-user override. If a human submits a review with “REQUEST CHANGES”, our
review-prtask aborts auto-merge by removing theautomergelabel. One extraifin the task does it.
Optional: Slack/Discord notification on merge
If you already have the Composio integration in your agent stack, adding a post-merge ping is three lines:
if (merged) {
await tools.slack.post("#deploys", `PR #${number} merged via OpenClaw.`);
}
This is outside the scope of GitHub, but worth mentioning because otherwise teammates wonder who pushed to release/2024-06-09.
Running on ClawCloud instead of self-hosting
ClawCloud gives you a TLS endpoint out of the box and rotates environment secrets automatically. Steps:
- New Agent → Blank TS template
- Paste the same
package.jsondeps (@openclaw/plugin-github). - Set secrets (
GITHUB_*...). - Deploy → wait ~60 s.
- Update the GitHub App’s Webhook URL to the Cloud hostname shown.
You can share the agent with team members; ClawCloud RBAC lets them read logs but not the PEM key.
What about GitHub Checks or ReviewDog?
You can have OpenClaw create a Check run instead of a plain comment. GitHub then surfaces the result in the PR UI sidebar. The plugin exposes github.createCheckRun(); swap it in place of createComment. ReviewDog integration works the same: output the diff-annotated JSON format ReviewDog expects and push as a check.
CI usage limits & cost considerations
Each review call to GPT-4o-mini runs ~1K tokens in + ~500 out. On Azure OpenAI that’s roughly $0.005. If you have 200 PRs a month you’re looking at coffee money. If you must stay free tier, switch to an open-weights model like mixtral-8x22B served via Ollama, bump model: "ollama:mixtral" and you’re done.
Takeaway & next step
You now have a GitHub App, a webhook, and an OpenClaw agent that reviews every PR, shouts when it sees a blocker, approves when it’s happy, and (only when labeled) merges into non-critical branches once CI passes. Ship it on a low-stakes repo first, read the audit logs, tweak the prompt, then scale out org-wide.