GitHub On-Call: PR Summaries and Automated Issue Handling
💡 One-sentence summary: use OpenClaw to set up a 7×24 GitHub on-call pipeline — the inbound webhook receives events, mapping hooks turn them into tasks, and the on-call agent produces summaries and replies.
Once a repository has many PRs, manually reading diffs falls behind; repeated questions in issues also go unanswered. This page walks you through building an OpenClaw GitHub on-call pipeline: inbound webhooks feed repository events to the on-call agent, new PRs automatically get a change summary, and new issues are automatically triaged with a drafted reply. Before starting, please complete Letting Claw Write Code: exec, Patches, and Approvals first and confirm your agent can finish a full task run. All examples are based on OpenClaw v2026.10.9.
What You'll Be Able to Do
- When a new PR enters the repository, the on-call agent automatically produces a change summary in a fixed format: what changed, which modules are affected, and where the risks are.
- New issues are automatically triaged: determining the type, identifying duplicates, and drafting a ready-to-send reply.
- Use curl to smoke-test the entire on-call pipeline locally, without waiting for a real PR to arrive.
- Non-target events (such as push) automatically return 204, without waking the agent or burning a round of conversation.
Why Use OpenClaw Instead of a Web-Based AI?
| Capability | Web-Based AI | OpenClaw |
|---|---|---|
| 7×24 responsiveness | ❌ If you don't open the page, it does nothing | ✅ The Gateway runs persistently and processes events as they arrive |
| Reads repository content | ❌ Manually pasting diffs, which get truncated when too long | ✅ The agent has a GitHub identity and pulls PR diffs and issue bodies itself |
| Event-driven | ❌ Manually copying links to ask questions | ✅ Inbound webhook + mapping hooks turn events into tasks automatically |
| Traceable troubleshooting | ❌ Once the conversation is closed, it's gone | ✅ Every request has a runId, and logs show the completion status |
| Permission boundaries | ❌ None | ✅ allowedAgentIds restricts which agents can be woken, and write operations still go through approvals |
Your Current Struggles
- You open the repository in the morning to find new PRs and issues lined up; clicking through each diff takes the whole morning.
- Duplicate issues keep getting filed; you want to triage and label them first, but that alone is very time-consuming.
- Manually pasting diffs into a web-based AI? Long diffs don't fit, context gets lost, and you still have to babysit the results.
When to Use This Technique
- The team repository gets new PRs and issues daily, and manual on-call duty can't keep up.
- You want uniformly formatted PR summaries so you can quickly decide which one to look at first.
- Issues need initial triage: categorizing, identifying duplicates, and giving the reporter a draft reply.
- You already have a 7×24 machine running the Gateway and have gotten exec and approvals working from the previous page.
🎒 Prerequisites
- A running Gateway whose agent can complete a full conversation round — this is the official prerequisite for webhooks.
- A long, random, dedicated token. The official docs explicitly require: do not reuse the Gateway auth token or password.
- Admin access to the GitHub repository, for adding the webhook.
- A public HTTPS entry point (a trusted reverse proxy): GitHub's servers must be able to reach your Gateway, and the proxy also adds the auth header (detailed in Step 4).
- An identity for the agent to read GitHub content: without
tools.githubconfigured,ghnative resolution is used; for a shared execution identity, configuretools.github(detailed in Step 5).
Follow Along
Step 1: Enable the Inbound Webhook and Light Up the OpenClaw GitHub On-Call Pipeline
Why this step first: the inbound webhook is disabled by default; without enabling it, GitHub events can't get into OpenClaw at all.
First, merge the hooks configuration block into the Gateway config. Replace the token with your dedicated long random value, and main with the on-call agent's id:
{
hooks: {
enabled: true,
token: "<long-random-hook-token>",
path: "/hooks",
allowedAgentIds: ["main"],
allowRequestSessionKey: false,
},
}Validate the config, restart the service, and watch the logs:
openclaw config validate
openclaw gateway restart
openclaw logs --followIf you run the Gateway in the foreground rather than as a service, stop that process manually and start it again.
You should see: openclaw config validate passes, and there are no hooks-related errors in the logs after the restart. While you're at it, get clear on where this set of endpoints sits: it falls under the Automations guide (Automations was unified under that name starting from OpenClaw 2.0), and its direction is "external services calling OpenClaw" — which is a different thing from internal event hooks (HOOK.md handlers) and from automated outbound webhook delivery. If the token reuses the Gateway shared-secret authentication (gateway.auth.token / OPENCLAW_GATEWAY_TOKEN, or gateway.auth.password / OPENCLAW_GATEWAY_PASSWORD), the startup log will record a non-fatal warning, and openclaw security audit will report it as critical. The fix: openclaw doctor --fix to rotate the persisted reused token, then update all external senders.
Step 2: Local smoke test of the on-call agent
Why smoke test: first verify on your local machine that the endpoint, token, and agent admission all work before letting external traffic in — if something breaks, it's easier to pinpoint.
Open another terminal and send a harmless test message to the local Gateway's /hooks/agent:
curl --include http://127.0.0.1:18789/hooks/agent \
-H 'Authorization: Bearer <long-random-hook-token>' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: webhook-smoke-001' \
--data '{"message":"Summarize this test event: the sample import completed.","name":"Webhook smoke test","agentId":"main","deliver":false}'You should see: HTTP 200, with the response body:
{ "ok": true, "runId": "<hook-request-run-id>" }Note what 200 means: it only indicates that the request was admitted to the session/globally; it does not mean the model finished running, that a tool succeeded, or that a message was delivered. A single request may wait up to 15 seconds before being admitted. In openclaw logs --follow, search for hook agent run completed and this runId: status=ok with no delivery error is logged at info level; non-ok statuses (including skipped runs), thrown errors, and explicit delivery errors are logged at warn level — status=ok but a warning with deliveryError means execution succeeded but delivery failed.
In the example, deliver: false is a sensible value for the smoke test: the success announcement is suppressed, the target field is ignored, and the completion result is only written to the log (non-ok outcomes still produce a failure event). When wiring up real GitHub events, choose as needed: if you want completion notifications in the on-call agent's main session, use the default deliver: true, which posts a completion system event in the target agent's main session; if you only want logs, keep false.
Step 3: Configure a mapping hook to turn the GitHub payload into an agent task
Why this step can't be skipped: the raw payload pushed by GitHub has no message field and can't carry the Authorization: Bearer authentication header, so pointing the Webhook directly at /hooks/agent will fail. The official mechanism for external events is the mapping hook: in hooks.mappings, configure a trusted JS/TS transform for a custom path, and it translates the payload into agent actions.
First, add a mapping to the hooks configuration (match.path: "gh" matches /hooks/gh):
{
hooks: {
enabled: true,
token: "<long-random-hook-token>",
path: "/hooks",
allowedAgentIds: ["main"],
allowRequestSessionKey: false,
mappings: [
{
id: "github",
match: { path: "gh" },
action: "agent",
agentId: "main",
transform: { module: "gh.mjs" },
},
],
},
}Then write the transform. The default directory is ~/.openclaw/hooks/transforms (config key transformsDir); the transform is trusted Gateway code and gets cached, so you must restart for changes to take effect:
// ~/.openclaw/hooks/transforms/gh.mjs
export default function transform({ payload, headers }) {
const event = headers["x-github-event"] ?? headers["X-GitHub-Event"];
if (event === "pull_request" && payload.action === "opened") {
const pr = payload.pull_request;
return {
kind: "agent",
message:
`新 PR #${payload.number}:${pr.title}\n` +
`作者 ${pr.user.login},分支 ${pr.head.ref} → ${pr.base.ref}\n` +
`请拉取该 PR 的 diff,按「PR 改动摘要」规范输出。`,
};
}
if (event === "issues" && payload.action === "opened") {
return {
kind: "agent",
message:
`新 issue #${payload.number}:${payload.issue.title}\n` +
`正文:${payload.issue.body ?? "(空)"}\n` +
`请按「issue 分诊与回复起草」规范分类并起草回复。`,
};
}
// 非目标事件(如 push):返回 null,OpenClaw 回 204,不创建 run
return null;
}The field names in the payload (action, number, pull_request, etc.) follow GitHub's official webhook delivery format — check them against an actually received event before wiring things up. If the event format is simple, you can skip writing JS and use the mapping's built-in messageTemplate template field to assemble the message; GitHub events need branching by type and filtering out non-target events, so a transform is used here.
Validate, restart, then locally simulate one delivery of each of the two event types:
openclaw config validate
openclaw gateway restart# 模拟新 PR:期望 200 + runId
curl --include http://127.0.0.1:18789/hooks/gh \
-H 'Authorization: Bearer <long-random-hook-token>' \
-H 'Content-Type: application/json' \
-H 'X-GitHub-Event: pull_request' \
--data '{"action":"opened","number":42,"pull_request":{"title":"Add export button","user":{"login":"alice"},"head":{"ref":"feat/export"},"base":{"ref":"main"}}}'
# 模拟 push:期望 204
curl --include http://127.0.0.1:18789/hooks/gh \
-H 'Authorization: Bearer <long-random-hook-token>' \
-H 'Content-Type: application/json' \
-H 'X-GitHub-Event: push' \
--data '{"ref":"refs/heads/main"}'You should see: the simulated PR returns HTTP 200 and { "ok": true, "runId": "<hook-request-run-id>" }; the simulated push returns HTTP 204 — the transform returned null, the mapping deliberately produces no action, and not even a run is created.
💡 This is the unique value of OpenClaw
Mapping hooks are a universal "external event translation layer": today you hook up GitHub; tomorrow, switching to another code hosting platform or a monitoring alert system is just a matter of swapping one transform file. Payload has no message? Generate it in the transform. Don't want irrelevant events burning a round of conversation? Return null, and OpenClaw replies 204. This is event-level wiring capability that web-based AI can't offer.
Step 4: Attach the Webhook to the GitHub repository
Why a reverse proxy is needed: the official requirement is that hook endpoints stay on loopback, a tailnet, or behind a trusted reverse proxy, with remote calls going over HTTPS. GitHub is on the public internet and your Gateway is on the internal network; a trusted reverse proxy forwards between them and adds the authentication header.
On the repository settings' Webhooks page, add a new hook:
- Payload URL:
https://<你的公网域名>/hooks/gh— forwarded by the reverse proxy to the Gateway, with theAuthorization: Bearer <long-random-hook-token>request header added. - Content type: choose JSON. OpenClaw's hook endpoint only accepts POSTs with
Content-Type: application/json. - Trigger events: check only Pull requests and Issues — don't enable all events. Non-target events will be blocked by the transform with 204 anyway, but there's no reason to let GitHub deliver them for nothing.
- Secret: the delivery signing key on the GitHub side @@I18N_KEEP_000025@@.
On the proxy side, expose only the one required path — don't leave the entire Gateway port naked on the public internet.
You should see: in GitHub's Webhook delivery records, target events return 200 and non-target events return 204; openclaw logs --follow shows hook agent run completed with the corresponding runId.
Step 5: Give the on-call agent a GitHub identity
Why this step is needed: the webhook payload only carries metadata like titles and authors; for reliable summaries and triage, the agent has to pull the PR diff and issue body itself, which requires a GitHub identity.
Option A (recommended to get running first): configure nothing. The GitHub CLI identity is resolved natively by default — the Gateway process's GH_TOKEN or GITHUB_TOKEN environment variables take priority, followed by the running user's gh keyring/config. Just complete gh auth login on the machine running the Gateway (or set up the environment variables for the Gateway process).
Option B (long-term on-call): configure a tools.github shared execution identity, with credentials managed by the Gateway. In the Control UI's Settings → Profile → GitHub connections, an admin selects For the system to initiate the connection: open https://github.com/login/device, paste the one-time code shown by OpenClaw, and approve the four permissions repo, workflow, read:org, gist. After a successful connection, a section like this appears in the configuration:
{
tools: {
github: {
profileId: "ghp_0123456789abcdef0123456789abcdef",
kind: "oauth",
gitAuthor: { name: "Automation User", email: "automation@example.com" },
},
},
}There are three boundaries to know:
- The OAuth access token expires after about 8 hours, and the Gateway refreshes it automatically before expiry; if the refresh token becomes invalid, it shows Reconnect required.
- The
kind: "app-installation"App identity only supports foreground session reads; to let the agent run tools, choose native or OAuth. - When a particular agent needs a different identity, override it in
agents.entries.<id>.tools.github; if not configured, it inherits the system identity. If you don't want to go through OAuth, you can also choose Use a PAT instead, preserving the setup path for fine-grained personal access tokens.
You should see: the on-call agent can pull the PR diff and issue body in its session; if a managed identity is configured but the credentials are missing or invalid, the GitHub status will report configured_unavailable rather than silently falling back to the local machine account.
📋 Magic Prompt
The transform is only responsible for translating the event into a single message; what the agent does with the message is determined by the standing instructions in its workspace. Write the two sections below into the on-call agent's AGENTS.md (by default at ~/.openclaw/workspace for a personal installation) — the names of these two specs are exactly what the transform-generated message references.
📝 PR Change Summary
Expected result: after a new PR event comes in, the agent produces a fixed-format summary — what changed, which modules are affected, where the risks are — so you can decide what to look at first without opening the diff.
Role: you are this repository's on-call reviewer, producing change summaries for newly opened PRs.
Task: pull the PR's diff and description, and produce a summary following the output spec.
Input information:
### Required fields
- 【PR number】
- 【PR title】
- 【Author】
- 【Branch (head → base)】
### Optional fields
- 【PR description?】
- 【Linked issue?】
Output spec:
- First line: one sentence summarizing what this PR does.
- Change list: grouped by module, one entry per line, labeled as added/modified/deleted.
- Impact scope: affected features, interfaces, or configuration.
- Risks: testing gaps, breaking changes, files that need careful manual review.
Constraints:
✅ Base everything only on the diff and the PR description; do not guess intentions that were not written down
✅ The summary must not exceed 20 lines
❌ Do not give review conclusions like "mergeable"; leave the decision to humans
❌ Do not modify code or push branches📋 Issue Triage and Reply Drafting
Expected outcome: new issues are automatically classified by type, duplicates are identified, and a reply ready to post directly is drafted.
Role: You are the on-call triager for this repository, triaging new issues and drafting replies.
Task: Read the issue title and body, determine its type, check for duplicates, and draft a reply.
Input information:
### Required fields
- 【Issue number】
- [Issue title]
### Optional fields
- [Issue body?]
- [List of numbers of similar issues in the repository?]
Output spec:
- Type: bug / feature request / documentation / usage question / other — pick one of the five.
- Duplicate check: if a likely duplicate is found, give the existing issue number; if none is found, write "no duplicate found".
- Reply draft: a Chinese reply that can be posted directly — confirm understanding and state the next step.
Constraints:
✅ When information is insufficient, list in the draft what the reporter needs to add
✅ Be specific and polite in tone; don't be dismissive
❌ Do not close issues on your own or modify repository files
❌ Do not promise fix timelines or versionsCheckpoints ✅
The standard for the whole on-call pipeline running end to end — check each item:
openclaw config validatepasses, and there are no hook errors in the logs after restart.- The curl smoke test in step 2 returns 200, and the response body contains
runId. - Step 3: simulating
pull_requestreturns 200, and simulatingpushreturns 204 (no run is created). - Step 4: 200/204 appears in GitHub's Webhook delivery records, and
openclaw logs --followshowshook agent run completedwhen searched. - Step 5: the on-call agent can pull the PR diff and the issue body within the session.
- The completion log is
status=ok; if you see a warn withstatus=okanddeliveryError, execution succeeded but delivery failed — check the delivery target.
Pitfall reminders
| Symptom | Most likely cause | How to handle it |
|---|---|---|
401 | The hook token is wrong, or the proxy doesn't forward the auth header | Verify the dedicated hook token (not the Gateway auth); check whether the proxy forwards the Authorization header as is |
404 | hooks.enabled, hooks.path, or the mapping path doesn't match | Confirm match.path: "gh" matches /hooks/gh; don't name a custom path agent or wake, and prefer the direct endpoint |
204 | The mapping intentionally produces no action | This is not an error: a transform returning null or an empty fan-out both result in 204 |
200 but no message received | deliver: false suppressed the success announcement | Check the completion log first; if you want delivery, configure explicit channel and to for the mapping |
500 | The transform threw an exception | Look at hook mapping failed in the logs, fix the transform, then restart |
Three security baselines — don't skip them:
- Dedicated token, dedicated subpath: keep hook token authentication separate from the Gateway shared-secret auth;
/as a path will be rejected. - Expose only the necessary paths: put the endpoint behind loopback, a tailnet, or a trusted reverse proxy; use HTTPS for remote calls.
- Treat external content as data: payloads are safety-wrapped by default, but the wrapper does not remove the agent's tools and workspace access — use a restricted agent for untrusted input and keep
allowUnsafeExternalContentoff.
Lesson summary
- An OpenClaw GitHub on-call pipeline consists of four pieces: an inbound Webhook as the door, a mapping hook as the translator, an on-call agent doing the work, and a GitHub identity to read the repository.
- Three things worth taking away:
hooks.mappingscan turn any external payload into agent actions; a transform returning null uses 204 to block non-target events without creating a run;deliverdecides whether completion events are delivered back to the main session. - Three security baselines: a dedicated token, expose only the necessary paths, and treat external content as data.
- Both PR summaries and issue triage produce only "drafts and judgments"; decisions like merging or closing stay with humans — this is on-call duty, not delegation of authority.
Next lesson preview
Once the on-call pipeline is running, you'll probably wonder: PR review and issue triage are crammed into the same agent's instructions — could we hire a specialist for each? The next lesson"Multi-Agent Collaboration: Running Multiple Specialists on One Gateway" covers how to run multiple mutually isolated agents in one Gateway — each with its own workspace and session history, using bindings to route different messages to different specialists, splitting today's on-call pipeline into a "PR specialist + issue specialist".