claude-beacon
v1.8.0
Published
Claude Code MCP channel plugin — pushes GitHub Actions CI/CD results into running Claude Code sessions
Downloads
205
Maintainers
Readme
claude-beacon
MCP channel plugin that pushes GitHub Actions CI/CD results and PR events directly into running Claude Code sessions — triggering automatic investigation and remediation.
Built on the Claude Code Channels API (research preview, ≥ v2.1.80).
What it does
| GitHub event | Condition | What Claude does |
|---|---|---|
| workflow_run completed | failure on main/master | Fetches logs, diagnoses root cause, spawns subagent to fix and push |
| workflow_run completed | failure on feature branch | Fetches logs, spawns subagent to investigate and fix |
| push to main/master | open PRs exist | Checks each PR's merge status; notifies on dirty or behind |
| pull_request | mergeable_state: dirty | Spawns subagent to rebase and resolve conflicts |
| pull_request | mergeable_state: behind | Spawns subagent to rebase cleanly |
| pull_request_review submitted | any non-APPROVED state | Debounced 30 s, then plan mode + pr-comment-response skill |
| pull_request_review_comment / issue_comment | — | Accumulated in the same debounce window |
| pull_request opened/ready | opt-in (on_pr_opened.enabled) | Notifies on new PRs opened or marked ready for review |
| pull_request_review APPROVED | opt-in (on_pr_approved.enabled) | Separate handler — e.g. auto-merge trigger |
| dependabot_alert created | opt-in (on_dependabot_alert.enabled) | Notifies about CVE — review and bump the dependency |
| code_scanning_alert created | opt-in (on_code_scanning_alert.enabled) | Notifies about SAST finding — review and apply a fix |
pushevents on main are the only way to detect PRs goingbehind— GitHub doesn't fire apull_requestevent when the base branch advances. Pushing to a feature branch does not trigger PR checks.
Quickstart
The fastest path is mux mode with a GitHub App: one App installation covers your entire org, and one persistent mux process handles all Claude Code sessions.
Requirements: Bun ≥ 1.1.0 · Claude Code ≥ 2.1.80 · cloudflared or ngrok
1. Install
bun add -g claude-beacon2. Set up secrets
openssl rand -hex 32 # generate a webhook secret — copy the output
echo 'GITHUB_WEBHOOK_SECRET=<paste-secret>' >> .env
echo 'GITHUB_TOKEN=<your-PAT>' >> .envGITHUB_TOKEN scopes: fine-grained → Actions: Read + Pull requests: Read; classic → public_repo.
3. Start the tunnel
cloudflared tunnel --url http://localhost:9443
# → prints: https://random-name.trycloudflare.com ← copy this URLKeep the tunnel running. For a stable URL that survives restarts (recommended): cloudflared named tunnel or ngrok static domain.
4. Create and install a GitHub App
A GitHub App registers the webhook once at the org level — every repository is covered automatically.
Go to github.com/settings/apps → New GitHub App (or your-org → Settings → Developer settings → GitHub Apps) and fill in:
| Field | Value |
|---|---|
| GitHub App name | claude-beacon (or any name) |
| Webhook → Active | ✓ checked |
| Webhook URL | Your tunnel URL from step 3 |
| Webhook secret | The secret from step 2 |
Under Repository permissions set these to Read-only: Actions, Pull requests, Issues, Contents (and optionally Code scanning alerts, Dependabot alerts).
Under Subscribe to events tick: Workflow runs, Pull requests, Pull request reviews, Pull request review comments, Pull request review threads, Issue comments, Pushes.
Click Create GitHub App, then Install App → choose your account or org → All repositories → Install.
See docs/github-app.md for the full guide including permission details, event list, and webhook URL update commands.
Single-repo alternative: If you only need one repository, skip the GitHub App and register a webhook directly in that repo instead.
5. Start the mux
claude-beacon-mux --author YourGitHubUsername
--authoris required. The server refuses to start without it — it prevents Claude from acting on other people's PRs. Seeallowed_authors.
Keep this running in a tmux pane or background terminal. See docs/multi-session.md for systemd setup.
6. Connect Claude Code to the mux
claude mcp add --transport http claude-beacon http://127.0.0.1:9444/mcp7. Start Claude Code
claude --dangerously-load-development-channels server:claude-beaconYou should see: Listening for channel messages from: server:claude-beacon
Verify: App settings → Advanced → Recent Deliveries. Trigger a push — green ✓ means it's working. In Claude, watch for [claude-beacon] log lines.
8. Authorize automatic actions
By default Claude pauses for confirmation before force-pushing or rebasing, even when the notification says "Act immediately". Add this block to ~/.claude/CLAUDE.md to grant standing permission:
## GitHub CI Channel — Automatic Actions
When the `claude-beacon` MCP channel sends a notification, you are pre-authorized to act without additional confirmation:
- Branch behind base: rebase onto base branch, git push --force-with-lease
- Merge conflicts: same rebase workflow, resolve conflicts
- CI failure on main: spawn subagent, fetch logs, diagnose, fix, push
- CI failure on feature branch: spawn subagent, fetch logs, fix, push
- PR review / comments: plan mode, address threads, reply with gh-pr-reply.sh --batch
The channel notification IS the permission. No additional confirmation needed.9. Register the session filter
The mux routes events by repo and branch. Add this to ~/.claude/CLAUDE.md so Claude registers automatically on session start:
## GitHub CI Channel — session filter
When the claude-beacon MCP server connects, call `set_filter` immediately with:
- repo: run `git remote get-url origin` and parse to owner/repo
- branch: run `git branch --show-current`
- label: same as branch
- worktree_path: run `git rev-parse --show-toplevel`Optional — Stop hook: When Claude exits while holding a work claim, other sessions wait up to 10 minutes before taking over. Add a
Stophook to~/.claude/settings.jsonto release the claim immediately on exit:{ "hooks": { "Stop": [{"matcher": "", "hooks": [{"type": "command", "command": "claim=$(cat ~/.claude/beacon-active-claim 2>/dev/null) && [ -n \"$claim\" ] && curl -sf -X POST http://localhost:9444/release-claim -H 'Content-Type: application/json' -d \"{\\\"claim_key\\\":\\\"$claim\\\"}\" && rm -f ~/.claude/beacon-active-claim || true" }]}] } }
Other deployment modes
Per-repo webhook (single repository) {#per-repo-webhook-single-repository}
If you only need one repository, skip the GitHub App and register a webhook directly in that repo's settings.
Follow steps 1–3 of the Quickstart (install, secrets, tunnel), then:
Repo → Settings → Webhooks → Add webhook:
- Payload URL — paste the tunnel URL
- Content type —
application/json - Secret — paste the webhook secret from step 2
- Events — select individually: Workflow runs, Workflow jobs, Check suites, Pull requests, Pull request reviews, Pull request review comments, Pull request review threads, Issue comments, Pushes
Then continue with Quickstart steps 5–9 (start mux, connect Claude, etc.).
The only difference from the GitHub App path: webhook URL must be updated manually when the tunnel restarts; new repos require a separate webhook registration.
Standalone (single Claude session)
If you only ever run one Claude Code window, skip the mux and let Claude Code spawn the server as a subprocess. Add to ~/.mcp.json or .mcp.json in your project:
{
"mcpServers": {
"claude-beacon": {
"command": "/home/you/.bun/bin/claude-beacon",
"args": ["--author", "YourGitHubUsername"],
"env": {
"GITHUB_WEBHOOK_SECRET": "your-webhook-secret",
"GITHUB_TOKEN": "your-pat"
}
}
}
}Use absolute paths (echo $HOME, which claude-beacon). Follow Quickstart steps 1–4 and 7–9; skip steps 5–6.
CLI Events watcher (no tunnel)
Polls the GitHub Events API using your existing gh CLI session — no tunnel or webhook config needed.
Trade-offs: ~30–60 s latency · WorkflowRunEvent only (no PR or job events) · no behind-PR detection.
{
"mcpServers": {
"claude-beacon": {
"command": "/home/you/.bun/bin/bun",
"args": ["run", "/path/to/claude-beacon/src/ghwatch.ts"],
"env": { "WATCH_REPOS": "owner/repo1,owner/repo2" }
}
}
}Hub mode (company-wide, multi-user)
Run a single claude-beacon-hub instance shared across a whole team or org. Each developer connects their Claude Code sessions with a personal Bearer token; events are routed by PR author to the right person's sessions. If a user's sessions are offline, an Anthropic SDK fallback worker handles the work and posts a summary to the PR.
No --author flag — in hub mode your GitHub identity comes from your Bearer token, not a CLI flag.
Minimal single-user setup
Good for a single developer who wants Bearer token auth (or plans to add teammates later):
1. Generate a token and create a minimal config:
bun add -g claude-beacon
TOKEN=$(openssl rand -hex 32)
echo "Your token: $TOKEN"
cat > hub-config.yaml << EOF
hub:
users:
- github_username: YourGitHubUsername
token: "$TOKEN"
EOF2. Start the hub (same machine as Claude Code — no reverse proxy needed):
GITHUB_WEBHOOK_SECRET=<secret> GITHUB_TOKEN=<pat> \
claude-beacon-hub --config hub-config.yaml3. Update your MCP config (replaces the old claude mcp add command from mux mode):
# Remove old mux entry if present
claude mcp remove claude-beacon
# Add hub entry with your Bearer token
claude mcp add --transport http claude-beacon http://127.0.0.1:9444/mcp \
--header "Authorization: Bearer $TOKEN"Or edit ~/.mcp.json directly:
{
"mcpServers": {
"claude-beacon": {
"url": "http://127.0.0.1:9444/mcp",
"type": "http",
"headers": { "Authorization": "Bearer <your-token>" }
}
}
}4. Start Claude Code and register the session filter — same as the Quickstart (steps 7–9).
The hub confirms: Filter registered for @YourGitHubUsername: owner/repo@feat/branch.
Team / company setup
For a shared instance accessible to the whole org, add a reverse proxy (nginx or Caddy) for TLS and point the url at your HTTPS endpoint. Each teammate gets their own token entry in hub:users. See docs/hub-mode.md for the full setup guide including reverse proxy config, systemd, fallback worker, and daemon sessions.
Multi-session coordination
When multiple Claude Code sessions receive the same notification, the claim API ensures only one acts:
claim_notification("<repo>:<branch>")
→ "ok" — you have the lock, proceed
→ "already_owned" — you already hold it, continue
→ "conflict:X" — session X claimed it first, stop
→ "expired" — claim timed out, stopClaims expire after 10 minutes (server.claim_ttl_ms). Release explicitly with release_claim("<key>") or automatically via the Stop hook (see Quickstart step 9).
If a webhook arrives before any session has called set_filter, the mux queues it (up to 50 events per repo, 2-hour TTL) and flushes it when a session registers.
Configuration
All settings are optional — the defaults work for most setups. Pass a YAML file with --config my-config.yaml (or via .mcp.json args). Environment variables (GITHUB_WEBHOOK_SECRET, GITHUB_TOKEN, WEBHOOK_PORT, REVIEW_DEBOUNCE_MS) always override YAML.
cp config.example.yaml my-config.yaml # start from the annotated template
claude-beacon-mux --author YourGitHubUsername --config my-config.yamlallowed_authors (required) {#allowed_authors-required}
claude-beacon refuses to start without at least one entry. Two kinds:
- Username (no
@) — matched againstpr.user.login, the PR author's GitHub handle. - Email (contains
@) — matched againstCo-Authored-Bycommit trailers. Use when an AI agent (Devin, etc.) creates the PR on your behalf.
webhooks:
allowed_authors:
- YourGitHubUsername
- [email protected] # for AI-agent co-authored PRsServer options
| Key | Default | Description |
|---|---|---|
| server.port | 9443 | HTTP port for the webhook receiver |
| server.debounce_ms | 30000 | Accumulate review events for this many ms before firing |
| server.cooldown_ms | 300000 | Suppress duplicate notifications for the same PR |
| server.max_events_per_window | 50 | Maximum review events buffered per debounce window |
| server.main_branches | ["main","master"] | Branch names treated as production |
| server.claim_ttl_ms | 600000 | How long a work-context claim is held before expiring |
Webhook filters
| Key | Default | Description |
|---|---|---|
| webhooks.allowed_authors | required | GitHub usernames and/or emails whose PRs trigger actions |
| webhooks.allowed_events | [] (all) | Allowlist of GitHub event types. Empty = accept all |
| webhooks.allowed_repos | [] (all) | Allowlist of repos as "owner/repo". Empty = accept all |
Behavior hooks
Each hook has an instruction template with {placeholder} substitution. Opt-in hooks default to enabled: false.
| Key | Triggered by | Flags |
|---|---|---|
| behavior.on_ci_failure_main | workflow_run failure on main | upstream_sync, use_agent |
| behavior.on_ci_failure_branch | workflow_run failure on feature branch | upstream_sync, use_agent |
| behavior.on_pr_review | PR review / comment events (debounced) | require_plan, skill, use_worktree |
| behavior.on_merge_conflict | PR with mergeable_state: dirty | — |
| behavior.on_branch_behind | PR with mergeable_state: behind | — |
| behavior.on_pr_opened | PR opened / ready for review | enabled (default false) |
| behavior.on_pr_approved | APPROVED review submitted | enabled (default false) |
| behavior.on_dependabot_alert | Dependabot CVE alert | enabled (default false), min_severity |
| behavior.on_code_scanning_alert | CodeQL / SAST alert | enabled (default false), min_severity |
behavior.code_style — free-form string prepended to every PR review notification. Describe your project's coding conventions here.
Notable flags:
use_agent—true(default) spawns a subagent to fix CI, keeping the main session free. Setfalseto act inline.upstream_sync—true(default) rebases from main before diagnosing. Setfalseif main is frequently broken.behavior.worktrees.mode—"temp"(default, shellgit worktree add/remove) or"native"(Claude Codeisolation="worktree").behavior.worktrees.base_dir— base directory for temporary worktrees (default/tmp). Path:{base_dir}/{repo}-pr-{N}-rebase.
Security alert hooks broadcast to all sessions registered for the repo. Enable only on the single instance responsible for security triage to avoid multiple sessions racing on the same CVE.
Template placeholders
| Placeholder | Available in |
|---|---|
| {repo} | all hooks |
| {branch} | CI failure hooks, code scanning |
| {run_url}, {workflow}, {status}, {commit} | CI failure hooks |
| {use_agent_preamble} | CI failure hooks (use_agent toggle) |
| {health_check_step} | CI failure hooks (upstream_sync toggle) |
| {pr_number}, {pr_title}, {pr_url} | PR state, on_pr_opened, on_pr_approved |
| {head_branch}, {base_branch} | PR state, on_pr_opened |
| {worktree_steps} | PR state (auto-generated rebase commands) |
| {skill}, {worktree_preamble} | on_pr_review |
| {author} | on_pr_opened |
| {reviewer} | on_pr_approved |
| {cve}, {package}, {patched_version} | on_dependabot_alert |
| {rule}, {tool} | on_code_scanning_alert |
| {severity}, {alert_url} | both security hooks |
Troubleshooting
MCP shows red / "Failed to reconnect"
Port 9443 is held by a previous session. Run lsof -i :9443, kill the PID, restart Claude Code.
401 Unauthorized on webhooks
Secret mismatch. Check GITHUB_WEBHOOK_SECRET in .mcp.json exactly matches GitHub. A .env in the repo directory can shadow it — delete it or make both match.
No notification when a PR falls behind
Ensure Pushes is ticked in GitHub webhook events and GITHUB_TOKEN is set. Note: only pushes to main/master trigger PR checks, not pushes to feature branches.
Mux sends to too many sessions
Stale sessions auto-expire after 30 minutes of inactivity. Restart the mux to clear them immediately.
"bun: command not found" in MCP logs
Use the absolute path in .mcp.json: "command": "/home/you/.bun/bin/bun". Find with which bun.
claude_desktop_config.json vs .mcp.json~/.config/Claude/claude_desktop_config.json is for Claude Desktop. ~/.mcp.json / .mcp.json is for Claude Code CLI. --dangerously-load-development-channels reads from .mcp.json.
Claude receives notifications but doesn't act automatically
The CLAUDE.md permissions block is missing — add it as described in Quickstart step 8.
No notifications ever arrive — checklist
- App → Advanced → Recent Deliveries → green ✓? If not, secret or URL is wrong.
- Tunnel still running? Restart = new URL → update the App's Webhook URL in settings.
- All required event types subscribed in App settings?
--authorexactly matches the GitHub login of the PR author (case-sensitive).- Claude Code started with
--dangerously-load-development-channels server:claude-beacon. - In mux mode:
set_filtercalled in the session?
GITHUB_TOKEN 401 on startup
The mux loads .env from its working directory, not your home directory. Confirm via the CWD log line at startup. For fine-grained tokens: resource owner must be the org, and the org must have approved the token.
Development
bun test # run tests
bun run typecheck # tsc --noEmit
bun run lint # Biome v2
bun run build # bundle to dist/Security: HMAC-SHA256 verification uses timingSafeEqual (constant-time). Raw payloads are never forwarded to Claude — only sanitized fields reach the notification. GITHUB_TOKEN is read-only. GITHUB_WEBHOOK_SECRET is required; set WEBHOOK_DEV_MODE=true to bypass in local dev only.
See AGENTS.md for the architecture reference and contributor guide.
