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_request and check_run events to a webhook URL.
  • The OpenClaw Gateway (Node 22+, @openclaw/core 0.37.2) listens on /api/github and 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

  1. Navigate to Settings → Developer settings → GitHub Apps → New GitHub App.
  2. App name: openclaw-pr-bot (any name is fine).
  3. Webhook URL: https://YOUR_GATEWAY_HOST/api/github. Set the content type to application/json.
  4. Webhook secret: generate a random string, e.g. pwgen 32 1. We’ll reference it as GITHUB_WEBHOOK_SECRET.
  5. Permissions (the minimum that works today, 2024-06-09):
    • ContentsRead-only (for diff fetching)
    • Pull requestsRead & Write (to comment, approve, merge)
    • ChecksRead-only (so the agent can see CI status)
    • MetadataRead-only (required by the API)
  6. Subscribe to events: enable Pull request and Check run.
  7. Create the app, then on the app page generate a Private key and download openclaw-pr-bot.pem.
  8. 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 page
  • INSTALLATION_ID – found under “Installations” → click repo → copy ID from the URL
  • PRIVATE_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 == automerge enqueue try-merge
  • check_run.completed –> re-enqueue try-merge so 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

  1. Fire up ngrok so GitHub can reach your laptop: ngrok http 3000
  2. Update the GitHub App’s Webhook URL to the ngrok HTTPS endpoint.
  3. Create a dummy PR targeting a non-protected branch (dev/test-automerge).
  4. Add the automerge label.
  5. Push a commit that passes CI (or stub the check with workflow_run).
  6. 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_run hits.
  • 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-retry headers 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:merge if something looks fishy.
  • Per-user override. If a human submits a review with “REQUEST CHANGES”, our review-pr task aborts auto-merge by removing the automerge label. One extra if in 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:

  1. New Agent → Blank TS template
  2. Paste the same package.json deps (@openclaw/plugin-github).
  3. Set secrets (GITHUB_*...).
  4. Deploy → wait ~60 s.
  5. 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.