claude-code-github-ci-channel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +45 -0
- package/README.md +543 -0
- package/config.example.yaml +153 -0
- package/dist/index.js +210 -0
- package/dist/mux.js +219 -0
- package/package.json +58 -0
package/.env.example
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# ── github-ci-channel environment variables ───────────────────────────────────
|
|
2
|
+
#
|
|
3
|
+
# Copy this file to .env in the repo root:
|
|
4
|
+
# cp .env.example .env
|
|
5
|
+
#
|
|
6
|
+
# Bun loads .env automatically from the working directory when you run
|
|
7
|
+
# any entry point directly (index.ts, mux.ts, ghwatch.ts).
|
|
8
|
+
#
|
|
9
|
+
# DO NOT commit .env — it is already in .gitignore.
|
|
10
|
+
#
|
|
11
|
+
# Mux mode: bun run src/mux.ts
|
|
12
|
+
# Standalone: bun run src/index.ts
|
|
13
|
+
# Polling: bun run src/ghwatch.ts
|
|
14
|
+
|
|
15
|
+
# ── Required ──────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
# HMAC-SHA256 secret shared with GitHub webhook settings.
|
|
18
|
+
# Generate: openssl rand -hex 32
|
|
19
|
+
# Must match the Secret field in GitHub → Settings → Webhooks exactly.
|
|
20
|
+
GITHUB_WEBHOOK_SECRET=
|
|
21
|
+
|
|
22
|
+
# Fine-grained PAT: Actions (Read) + Pull requests (Read)
|
|
23
|
+
# Classic PAT: public_repo
|
|
24
|
+
# Used by fetch_workflow_logs and behind-PR detection after a push to main.
|
|
25
|
+
GITHUB_TOKEN=
|
|
26
|
+
|
|
27
|
+
# ── Optional — webhook receiver ───────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
# HTTP port for incoming GitHub webhooks (default: 9443).
|
|
30
|
+
# WEBHOOK_PORT=9443
|
|
31
|
+
|
|
32
|
+
# Skip HMAC verification. ONLY for local dev without a real webhook secret.
|
|
33
|
+
# NEVER enable in production.
|
|
34
|
+
# WEBHOOK_DEV_MODE=false
|
|
35
|
+
|
|
36
|
+
# ── Optional — mux server (src/mux.ts only) ───────────────────────────────────
|
|
37
|
+
|
|
38
|
+
# Localhost-only port for MCP Streamable HTTP (default: 9444).
|
|
39
|
+
# Claude Code sessions connect to http://127.0.0.1:${MCP_PORT}/mcp
|
|
40
|
+
# MCP_PORT=9444
|
|
41
|
+
|
|
42
|
+
# ── Optional — GitHub polling watcher (src/ghwatch.ts only) ──────────────────
|
|
43
|
+
|
|
44
|
+
# Comma-separated list of repositories to watch (owner/repo).
|
|
45
|
+
# WATCH_REPOS=myorg/frontend,myorg/backend
|
package/README.md
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
# claude-code-github-ci-channel
|
|
2
|
+
|
|
3
|
+
[](https://github.com/drmf-cz/claude-code-github-ci-channel/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/claude-code-github-ci-channel)
|
|
5
|
+
|
|
6
|
+
> MCP channel plugin that pushes GitHub Actions CI/CD results and PR merge status directly into running Claude Code sessions — triggering automatic investigation and remediation.
|
|
7
|
+
|
|
8
|
+
Built on the [Claude Code Channels API](https://docs.anthropic.com/en/docs/claude-code/channels) (research preview, ≥ v2.1.80).
|
|
9
|
+
|
|
10
|
+
## What it does
|
|
11
|
+
|
|
12
|
+
The plugin runs inside your Claude Code session and listens for GitHub events. When something requires attention, it pushes an actionable instruction directly into the session — Claude reads it and acts on it immediately.
|
|
13
|
+
|
|
14
|
+
| GitHub event | Condition | What Claude does |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `workflow_run` completed | failure on **main/master** | Fetches logs, diagnoses root cause, spawns subagent to fix and push |
|
|
17
|
+
| `workflow_run` completed | failure on feature branch | Fetches logs, spawns subagent to investigate |
|
|
18
|
+
| `workflow_run` completed | success | Silent — no notification |
|
|
19
|
+
| `push` to main/master | open PRs exist | Checks each PR's merge status via API, notifies on `dirty` or `behind` |
|
|
20
|
+
| `pull_request` opened/synced | `mergeable_state: dirty` | Spawns subagent to rebase and resolve conflicts |
|
|
21
|
+
| `pull_request` opened/synced | `mergeable_state: behind` | Spawns subagent to rebase cleanly |
|
|
22
|
+
| `pull_request_review` submitted | any non-draft state | Debounced 30 s, then enters plan mode + `pr-comment-response` skill |
|
|
23
|
+
| `pull_request_review_comment` created | — | Accumulated in same debounce window |
|
|
24
|
+
| `pull_request_review_thread` unresolved | thread re-opened | Accumulated in same debounce window, shown as 🔄 |
|
|
25
|
+
| `issue_comment` created | PR comment (not issue) | Accumulated in same debounce window |
|
|
26
|
+
|
|
27
|
+
> **Why `push` events for PRs?** GitHub does not fire a `pull_request` event when the base branch advances and makes a PR go `behind`. The only way to detect this is to listen to `push` on main and then query the API for open PRs.
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
GitHub Actions / PR / push event
|
|
33
|
+
│ HMAC-SHA256 signed webhook
|
|
34
|
+
▼
|
|
35
|
+
[cloudflared tunnel] ← free, no account needed for temp URLs
|
|
36
|
+
│
|
|
37
|
+
▼
|
|
38
|
+
HTTP :9443 (webhook receiver — subprocess of Claude Code)
|
|
39
|
+
│
|
|
40
|
+
├─ workflow_run/job/check_suite → parseWorkflowEvent()
|
|
41
|
+
├─ pull_request (dirty/behind) → parsePullRequestEvent()
|
|
42
|
+
└─ push to main → checkPRsAfterPush() [async, API call]
|
|
43
|
+
│
|
|
44
|
+
▼
|
|
45
|
+
notifications/claude/channel
|
|
46
|
+
│
|
|
47
|
+
▼
|
|
48
|
+
Claude Code session
|
|
49
|
+
├─ fetch_workflow_logs tool
|
|
50
|
+
└─ spawns subagents to fix/rebase
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The MCP server is started automatically by Claude Code as a subprocess — you never run it manually.
|
|
54
|
+
|
|
55
|
+
## Requirements
|
|
56
|
+
|
|
57
|
+
- [Bun](https://bun.sh) ≥ 1.1.0 — the package runs on Bun; `npx` will not work
|
|
58
|
+
- Claude Code ≥ 2.1.80
|
|
59
|
+
- [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) (or ngrok)
|
|
60
|
+
- GitHub PAT — fine-grained: **Actions: Read** + **Pull requests: Read** | classic: `public_repo`
|
|
61
|
+
|
|
62
|
+
> `GITHUB_TOKEN` is required for two features: `fetch_workflow_logs` (log fetching) and `checkPRsAfterPush` (listing open PRs after a push). Without it, those features silently no-op.
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
No cloning required. Install once with Bun:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
bun add -g claude-code-github-ci-channel
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This puts two binaries in `~/.bun/bin/`:
|
|
73
|
+
|
|
74
|
+
| Binary | Purpose |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `github-ci` | Standalone MCP server — spawned as subprocess by Claude Code (single session) |
|
|
77
|
+
| `github-ci-mux` | Mux server — run once, connects all Claude Code sessions via HTTP |
|
|
78
|
+
|
|
79
|
+
To update to a newer version: `bun add -g claude-code-github-ci-channel@latest`
|
|
80
|
+
|
|
81
|
+
> **No global install?** You can also run directly with `bunx`:
|
|
82
|
+
> ```bash
|
|
83
|
+
> bunx claude-code-github-ci-channel # standalone (github-ci)
|
|
84
|
+
> bunx -p claude-code-github-ci-channel github-ci-mux # mux
|
|
85
|
+
> ```
|
|
86
|
+
|
|
87
|
+
## Setup (Option A — Webhook + Tunnel)
|
|
88
|
+
|
|
89
|
+
Real-time. Supports all event types including `workflow_job`, `check_suite`, and PR status.
|
|
90
|
+
|
|
91
|
+
### 1. Install
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
bun add -g claude-code-github-ci-channel
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Or clone if you want to hack on the source:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
git clone https://github.com/drmf-cz/claude-code-github-ci-channel
|
|
101
|
+
cd claude-code-github-ci-channel
|
|
102
|
+
bun install
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 2. Generate a webhook secret
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
openssl rand -hex 32
|
|
109
|
+
# → copy the output — you'll paste it into GitHub and .mcp.json
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 3. Start the tunnel
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
cloudflared tunnel --url http://localhost:9443
|
|
116
|
+
# → prints: https://random-name.trycloudflare.com ← copy this URL
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Leave the tunnel running. Each restart produces a new URL — update GitHub when that happens.
|
|
120
|
+
For a stable URL: [Cloudflare named tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/) (free account) or [ngrok static domain](https://ngrok.com/blog-post/free-static-domains-ngrok-users) (free tier).
|
|
121
|
+
|
|
122
|
+
### 4. Register the webhook on GitHub
|
|
123
|
+
|
|
124
|
+
1. Repo → **Settings → Webhooks → Add webhook**
|
|
125
|
+
2. **Payload URL** — paste your tunnel URL
|
|
126
|
+
3. **Content type** — `application/json`
|
|
127
|
+
4. **Secret** — paste the value from step 2
|
|
128
|
+
5. **Which events** — choose *Let me select individual events*, then tick:
|
|
129
|
+
- ✅ Workflow runs
|
|
130
|
+
- ✅ Workflow jobs
|
|
131
|
+
- ✅ Check suites
|
|
132
|
+
- ✅ Pull requests
|
|
133
|
+
- ✅ Pull request reviews
|
|
134
|
+
- ✅ Pull request review comments
|
|
135
|
+
- ✅ Pull request review threads
|
|
136
|
+
- ✅ Issue comments
|
|
137
|
+
- ✅ Pushes
|
|
138
|
+
6. Click **Add webhook** — GitHub sends a ping; you should see a green ✓
|
|
139
|
+
|
|
140
|
+
### 5. Configure `.mcp.json`
|
|
141
|
+
|
|
142
|
+
Create or edit `~/.mcp.json` (all projects) or `.mcp.json` in your project root.
|
|
143
|
+
|
|
144
|
+
**After global install** (`bun add -g`):
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"mcpServers": {
|
|
149
|
+
"github-ci": {
|
|
150
|
+
"command": "/home/you/.bun/bin/github-ci",
|
|
151
|
+
"env": {
|
|
152
|
+
"GITHUB_WEBHOOK_SECRET": "your-secret-from-step-2",
|
|
153
|
+
"GITHUB_TOKEN": "your-pat"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Replace `/home/you` with your home directory (`echo $HOME`). Bun installs globals to `~/.bun/bin/`.
|
|
161
|
+
|
|
162
|
+
**If you cloned the repo** (or prefer `bunx` for always-latest):
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"mcpServers": {
|
|
167
|
+
"github-ci": {
|
|
168
|
+
"command": "/home/you/.bun/bin/bunx",
|
|
169
|
+
"args": ["claude-code-github-ci-channel"],
|
|
170
|
+
"env": {
|
|
171
|
+
"GITHUB_WEBHOOK_SECRET": "your-secret-from-step-2",
|
|
172
|
+
"GITHUB_TOKEN": "your-pat"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
> Claude Code spawns MCP subprocesses without your shell PATH, so always use absolute paths to binaries. Find them with `which github-ci` or `which bunx`.
|
|
180
|
+
|
|
181
|
+
### 6. Start Claude Code
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
claude --dangerously-load-development-channels server:github-ci
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
You should see:
|
|
188
|
+
```
|
|
189
|
+
Listening for channel messages from: server:github-ci
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The server is now running. Push a commit, trigger a CI run, or let a PR fall behind — notifications will appear in your session automatically.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Option B — GitHub CLI Events watcher (no tunnel)
|
|
197
|
+
|
|
198
|
+
No tunnel, no webhook config. Polls the [GitHub Events API](https://docs.github.com/en/rest/activity/events) using your existing `gh` CLI session.
|
|
199
|
+
|
|
200
|
+
**Trade-offs vs Option A:**
|
|
201
|
+
- ~30–60 s latency (server-dictated poll interval)
|
|
202
|
+
- `WorkflowRunEvent` only — no `workflow_job`, `check_suite`, or PR events
|
|
203
|
+
- No "behind PR" detection (Events API doesn't include pull request merge status)
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"mcpServers": {
|
|
208
|
+
"github-ci": {
|
|
209
|
+
"command": "/home/you/.bun/bin/bun",
|
|
210
|
+
"args": ["run", "/path/to/claude-code-github-ci-channel/src/ghwatch.ts"],
|
|
211
|
+
"env": {
|
|
212
|
+
"WATCH_REPOS": "owner/repo1,owner/repo2"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Auth: uses `gh auth token` automatically. Override with `GITHUB_TOKEN` if needed.
|
|
220
|
+
|
|
221
|
+
Start the same way:
|
|
222
|
+
```bash
|
|
223
|
+
claude --dangerously-load-development-channels server:github-ci
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Environment variables
|
|
229
|
+
|
|
230
|
+
| Variable | Option A | Option B | Description |
|
|
231
|
+
|---|---|---|---|
|
|
232
|
+
| `WEBHOOK_PORT` | optional | — | HTTP port for webhook receiver (default: `9443`) |
|
|
233
|
+
| `GITHUB_WEBHOOK_SECRET` | required | — | HMAC-SHA256 secret — must match GitHub webhook settings exactly |
|
|
234
|
+
| `GITHUB_TOKEN` | required* | optional | PAT for log fetching and PR status checks |
|
|
235
|
+
| `WATCH_REPOS` | — | required | Comma-separated `owner/repo` list |
|
|
236
|
+
| `REVIEW_DEBOUNCE_MS` | optional | — | Debounce window for review events (default: `30000` ms) |
|
|
237
|
+
|
|
238
|
+
\* Without `GITHUB_TOKEN`, `fetch_workflow_logs` and behind-PR detection silently no-op.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Running multiple Claude Code sessions (mux server)
|
|
243
|
+
|
|
244
|
+
By default the server is spawned as a subprocess by Claude Code and binds port 9443. A second session fails to bind that port and misses all events.
|
|
245
|
+
|
|
246
|
+
The **mux server** (`src/mux.ts`) solves this: you run it once as a persistent process and every Claude Code session connects to it via a single local URL — no subprocess is spawned per session.
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
GitHub ──► :9443 (webhook receiver)
|
|
250
|
+
│
|
|
251
|
+
[mux — you start this once]
|
|
252
|
+
│
|
|
253
|
+
:9444/mcp (MCP over HTTP — localhost only)
|
|
254
|
+
┌────┴────┐
|
|
255
|
+
Session A Session B Session C …
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Quick setup
|
|
259
|
+
|
|
260
|
+
**1. Create your `.env` file with your secrets:**
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
cp .env.example .env
|
|
264
|
+
# Edit .env — fill in GITHUB_WEBHOOK_SECRET and GITHUB_TOKEN
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**2. Start the mux once** (tmux pane, background terminal, or [systemd unit](docs/multi-session.md#running-as-a-systemd-unit)):
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
# After global install (recommended)
|
|
271
|
+
github-ci-mux # reads .env from current directory
|
|
272
|
+
github-ci-mux --config my-config.yaml # optional YAML config
|
|
273
|
+
|
|
274
|
+
# Or via bunx (no install)
|
|
275
|
+
bunx -p claude-code-github-ci-channel github-ci-mux
|
|
276
|
+
|
|
277
|
+
# Or from cloned repo
|
|
278
|
+
bun run start:mux
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**3. Register the mux in Claude Code** (run once — applies to all projects):
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
claude mcp add --transport http github-ci http://127.0.0.1:9444/mcp
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Or add to `.mcp.json` in your project:
|
|
288
|
+
|
|
289
|
+
```json
|
|
290
|
+
{
|
|
291
|
+
"mcpServers": {
|
|
292
|
+
"github-ci": {
|
|
293
|
+
"url": "http://127.0.0.1:9444/mcp",
|
|
294
|
+
"type": "http"
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**4. Start Claude Code normally:**
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
claude --dangerously-load-development-channels server:github-ci
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**5. Register your session filter** — tell the mux which repo and branch this session is watching:
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
Call set_filter with the output of:
|
|
310
|
+
git remote get-url origin → parse to owner/repo
|
|
311
|
+
git branch --show-current → current branch
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
To make this automatic, add to `~/.claude/CLAUDE.md`:
|
|
315
|
+
|
|
316
|
+
```markdown
|
|
317
|
+
## GitHub CI Channel — session filter
|
|
318
|
+
When github-ci MCP connects, call `set_filter` immediately:
|
|
319
|
+
run `git remote get-url origin` (parse to owner/repo) and
|
|
320
|
+
`git branch --show-current`, then call set_filter with those values.
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
For full details — routing rules, systemd setup, comparison table — see **[docs/multi-session.md](docs/multi-session.md)**.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## YAML configuration file
|
|
328
|
+
|
|
329
|
+
For anything beyond the five environment variables, use a YAML config file. It lets you tune debounce windows, filter which repos or event types trigger Claude, and — most importantly — replace the built-in agent instructions with your own.
|
|
330
|
+
|
|
331
|
+
### Quick start
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
cp config.example.yaml my-config.yaml
|
|
335
|
+
# edit my-config.yaml to taste
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Pass it via `.mcp.json`:
|
|
339
|
+
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"mcpServers": {
|
|
343
|
+
"github-ci": {
|
|
344
|
+
"command": "/home/you/.bun/bin/bun",
|
|
345
|
+
"args": [
|
|
346
|
+
"run", "/path/to/claude-code-github-ci-channel/src/index.ts",
|
|
347
|
+
"--config", "/path/to/my-config.yaml"
|
|
348
|
+
],
|
|
349
|
+
"env": {
|
|
350
|
+
"GITHUB_WEBHOOK_SECRET": "...",
|
|
351
|
+
"GITHUB_TOKEN": "..."
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Environment variables (`WEBHOOK_PORT`, `GITHUB_WEBHOOK_SECRET`, `GITHUB_TOKEN`) always take precedence over their YAML equivalents. All fields are optional — omitted keys inherit from the defaults below.
|
|
359
|
+
|
|
360
|
+
### Server options
|
|
361
|
+
|
|
362
|
+
| Key | Type | Default | Description |
|
|
363
|
+
|---|---|---|---|
|
|
364
|
+
| `server.port` | number | `9443` | HTTP port for the webhook receiver |
|
|
365
|
+
| `server.debounce_ms` | number | `30000` | How long (ms) to accumulate review events before firing |
|
|
366
|
+
| `server.cooldown_ms` | number | `300000` | Suppress duplicate notifications for the same PR within this window |
|
|
367
|
+
| `server.max_events_per_window` | number | `50` | Maximum review events buffered per debounce window |
|
|
368
|
+
| `server.main_branches` | string[] | `["main","master"]` | Branch names treated as production |
|
|
369
|
+
|
|
370
|
+
### Webhook filters
|
|
371
|
+
|
|
372
|
+
| Key | Type | Default | Description |
|
|
373
|
+
|---|---|---|---|
|
|
374
|
+
| `webhooks.allowed_events` | string[] | `[]` (all) | Allowlist of GitHub event types to process. Empty = accept all supported types |
|
|
375
|
+
| `webhooks.allowed_repos` | string[] | `[]` (all) | Allowlist of repos as `"owner/repo"`. Empty = accept all |
|
|
376
|
+
|
|
377
|
+
Supported event type strings: `push`, `workflow_run`, `workflow_job`, `check_suite`, `pull_request`, `pull_request_review`, `pull_request_review_comment`, `pull_request_review_thread`, `issue_comment`.
|
|
378
|
+
|
|
379
|
+
### Behavior — agent instructions
|
|
380
|
+
|
|
381
|
+
These control what Claude is asked to do when an event fires. Each field accepts a multi-line string. Placeholders are interpolated at runtime (see [Template placeholders](#template-placeholders)).
|
|
382
|
+
|
|
383
|
+
| Key | Triggered by | Notable sub-fields |
|
|
384
|
+
|---|---|---|
|
|
385
|
+
| `behavior.on_ci_failure_main.instruction` | `workflow_run` failure on a main branch | — |
|
|
386
|
+
| `behavior.on_ci_failure_branch.instruction` | `workflow_run` failure on any other branch | — |
|
|
387
|
+
| `behavior.on_pr_review.instruction` | PR review / comment events (after debounce) | `require_plan`, `skill` |
|
|
388
|
+
| `behavior.on_merge_conflict.instruction` | PR with `mergeable_state: dirty` | — |
|
|
389
|
+
| `behavior.on_branch_behind.instruction` | PR with `mergeable_state: behind` | — |
|
|
390
|
+
|
|
391
|
+
**`on_pr_review` sub-fields:**
|
|
392
|
+
|
|
393
|
+
| Key | Type | Default | Description |
|
|
394
|
+
|---|---|---|---|
|
|
395
|
+
| `behavior.on_pr_review.require_plan` | boolean | `true` | Whether Claude must enter plan mode before touching code |
|
|
396
|
+
| `behavior.on_pr_review.skill` | string | `"pr-comment-response"` | Skill name invoked to handle the review |
|
|
397
|
+
|
|
398
|
+
**Code style context:**
|
|
399
|
+
|
|
400
|
+
```yaml
|
|
401
|
+
behavior:
|
|
402
|
+
code_style: |
|
|
403
|
+
- Use TypeScript strict mode; never cast to `any`
|
|
404
|
+
- Prefer `const` over `let`; avoid mutation
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
The `code_style` string is prepended to every PR review notification so Claude applies consistent standards when addressing comments.
|
|
408
|
+
|
|
409
|
+
### Template placeholders
|
|
410
|
+
|
|
411
|
+
The following placeholders are replaced at runtime inside any `instruction` string:
|
|
412
|
+
|
|
413
|
+
| Placeholder | Value |
|
|
414
|
+
|---|---|
|
|
415
|
+
| `{repo}` | `owner/repo` |
|
|
416
|
+
| `{branch}` | Branch name |
|
|
417
|
+
| `{pr_number}` | Pull request number |
|
|
418
|
+
| `{workflow_name}` | Workflow / check name |
|
|
419
|
+
| `{run_id}` | GitHub Actions run ID |
|
|
420
|
+
|
|
421
|
+
### Minimal example
|
|
422
|
+
|
|
423
|
+
```yaml
|
|
424
|
+
server:
|
|
425
|
+
debounce_ms: 10000 # faster review batching
|
|
426
|
+
main_branches: [main, release]
|
|
427
|
+
|
|
428
|
+
webhooks:
|
|
429
|
+
allowed_repos:
|
|
430
|
+
- my-org/my-repo # only watch this repo
|
|
431
|
+
|
|
432
|
+
behavior:
|
|
433
|
+
code_style: |
|
|
434
|
+
- Go 1.23+; run gofmt before committing
|
|
435
|
+
- All exported symbols must have godoc comments
|
|
436
|
+
on_pr_review:
|
|
437
|
+
require_plan: true
|
|
438
|
+
skill: pr-comment-response
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Migrating from environment variables
|
|
442
|
+
|
|
443
|
+
| Old env var | YAML equivalent |
|
|
444
|
+
|---|---|
|
|
445
|
+
| `WEBHOOK_PORT` | `server.port` |
|
|
446
|
+
| `REVIEW_DEBOUNCE_MS` | `server.debounce_ms` |
|
|
447
|
+
|
|
448
|
+
`GITHUB_WEBHOOK_SECRET` and `GITHUB_TOKEN` have no YAML equivalent — keep them in the `env` block inside `.mcp.json` (secrets should not live in a config file that could be committed to source control).
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Granting Claude automatic action permissions
|
|
453
|
+
|
|
454
|
+
By default Claude Code will pause and ask for confirmation before rebasing or force-pushing, even when the channel notification says "Act immediately". This is because Claude's `CLAUDE.md` rules rank above channel message instructions — which is correct security behaviour.
|
|
455
|
+
|
|
456
|
+
To authorize automatic actions, add this block to `~/.claude/CLAUDE.md`:
|
|
457
|
+
|
|
458
|
+
```markdown
|
|
459
|
+
## GitHub CI Channel — Automatic Actions
|
|
460
|
+
|
|
461
|
+
When the `github-ci` MCP channel sends a notification, you are pre-authorized to act without additional confirmation:
|
|
462
|
+
|
|
463
|
+
- **Branch behind base**: Create a git worktree at `/tmp/pr-<N>-rebase`, rebase onto the base branch, `git push --force-with-lease`, remove the worktree.
|
|
464
|
+
- **Merge conflicts**: Same worktree workflow; resolve conflicts during the interactive rebase.
|
|
465
|
+
- **CI failure on main**: Spawn a subagent — call `fetch_workflow_logs`, diagnose the root cause, apply a fix, push to restore main.
|
|
466
|
+
- **CI failure on feature branch**: Spawn a subagent — fetch logs, investigate, push a fix.
|
|
467
|
+
- **PR review / comments**: Enter plan mode, then use the `pr-comment-response` skill to address each thread. Apply code fixes in a worktree, reply to comments with `gh-pr-reply.sh --batch`.
|
|
468
|
+
|
|
469
|
+
The channel notification IS the permission. No additional confirmation needed for any of the above.
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
**Why this is required:** Channel messages cannot grant permissions — only `CLAUDE.md` (or direct user instruction) can. The notification text "Act immediately" is a hint to Claude, but any conflicting `CLAUDE.md` rule wins. Adding the block above creates a standing permission that makes the channel notification a valid trigger rather than an override attempt.
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## Troubleshooting
|
|
477
|
+
|
|
478
|
+
### MCP shows red / "Failed to reconnect"
|
|
479
|
+
|
|
480
|
+
Port 9443 is likely still held by a previous session. Claude Code spawns the server as a subprocess — if an old one didn't exit cleanly, the new one fails to bind.
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
lsof -i :9443 # find the process
|
|
484
|
+
kill <PID> # free the port
|
|
485
|
+
# restart Claude Code
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
The server logs: `ERROR: Port 9443 is already in use. Kill the existing process (lsof -i :9443) and restart Claude Code.`
|
|
489
|
+
|
|
490
|
+
### Webhooks return 401 Unauthorized
|
|
491
|
+
|
|
492
|
+
HMAC signature mismatch — the server and GitHub are using different secrets.
|
|
493
|
+
|
|
494
|
+
- **Most common cause:** a `.env` file in the repo directory has a different `GITHUB_WEBHOOK_SECRET` than `.mcp.json`. Bun loads `.env` automatically, but `.mcp.json` values take precedence. If you have both, make sure they match, or delete `.env`.
|
|
495
|
+
- Also check that the secret in `.mcp.json` exactly matches what was pasted into GitHub webhook settings (no extra whitespace).
|
|
496
|
+
|
|
497
|
+
### No notification when a PR falls behind
|
|
498
|
+
|
|
499
|
+
Check that **Pushes** is ticked in your GitHub webhook event settings. Without push events, the server never knows main has advanced. Also confirm `GITHUB_TOKEN` is set — it's required to query the PR list after a push.
|
|
500
|
+
|
|
501
|
+
### "bun: command not found" in MCP logs
|
|
502
|
+
|
|
503
|
+
Use the absolute path in `.mcp.json`: `"command": "/home/you/.bun/bin/bun"`. Find the path with `which bun`.
|
|
504
|
+
|
|
505
|
+
### `claude_desktop_config.json` vs `.mcp.json`
|
|
506
|
+
|
|
507
|
+
These are for different apps:
|
|
508
|
+
- `~/.config/Claude/claude_desktop_config.json` → Claude **Desktop** (GUI)
|
|
509
|
+
- `~/.mcp.json` or `.mcp.json` → Claude Code **CLI** (`claude` command)
|
|
510
|
+
|
|
511
|
+
`--dangerously-load-development-channels` reads from `.mcp.json`, not the Desktop config.
|
|
512
|
+
|
|
513
|
+
### Tunnel URL changed
|
|
514
|
+
|
|
515
|
+
Cloudflared free tier gives a new random URL on each restart. Update it: Repo → Settings → Webhooks → Edit → change Payload URL. For a permanent URL use a named tunnel or ngrok static domain.
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Development
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
bun test # 32 tests
|
|
523
|
+
bun run typecheck # tsc --noEmit (strict + noUncheckedIndexedAccess)
|
|
524
|
+
bun run lint # Biome v2
|
|
525
|
+
bun run lint:fix # Auto-fix lint issues
|
|
526
|
+
bun run build # Bundle to dist/
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Linting
|
|
530
|
+
|
|
531
|
+
Biome v2 is configured in `biome.json` with strict rules. One deliberate deviation from the defaults:
|
|
532
|
+
|
|
533
|
+
- **`maxAllowedComplexity: 45`** — Biome's recommended default is 15. The main webhook `fetch()` handler in `server.ts` is a single large dispatcher that handles all event types; its cyclomatic complexity scores ~40 after the dispatcher refactors in this codebase. Splitting it further would obscure the control flow. The threshold is set just above the real worst-offender score — if it ever exceeds 45, that's a signal to refactor.
|
|
534
|
+
|
|
535
|
+
## Security
|
|
536
|
+
|
|
537
|
+
- HMAC-SHA256 verification uses `timingSafeEqual` — constant-time, no timing oracle
|
|
538
|
+
- Fallback handler emits only `event + action + repo` — raw payload is never forwarded to Claude (prompt injection guard)
|
|
539
|
+
- `GITHUB_TOKEN` is read-only (`actions:read` + `pull_requests:read`) — no write access needed
|
|
540
|
+
- `.env` is gitignored — secrets stay local
|
|
541
|
+
- `GITHUB_WEBHOOK_SECRET` is **required** — omitting it causes all requests to be rejected; set `WEBHOOK_DEV_MODE=true` to bypass verification in local dev only
|
|
542
|
+
|
|
543
|
+
See [AGENTS.md](AGENTS.md) for the full security analysis and architecture reference.
|