ci-channel
v0.5.4
Published
Claude Code channel plugin for real-time CI/CD failure notifications (GitHub Actions, GitLab CI, Gitea Actions)
Maintainers
Readme
CI Channel for Claude Code
Real-time CI/CD notifications delivered straight into your Claude Code session. When a CI workflow or pipeline fails, Claude sees it immediately and can investigate the failure, check logs, and suggest fixes — without you having to context-switch.
Supports GitHub Actions, GitLab CI, and Gitea Actions.
How It Works
Forge (GitHub/GitLab/Gitea)
│
webhook POST
│
▼
smee.io (relay) ← auto-provisioned on first run
│
▼
localhost:{port} ← random port, no conflicts
│
▼
┌───────────────────┐
│ Channel Plugin │ ← validates signature, deduplicates, filters
│ (MCP server) │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ Claude Code │ ← receives structured notification
│ (session) │
└───────────────────┘Quick Start (One Command)
Run this from inside the project you want to monitor:
# GitHub (default)
cd /path/to/your-project
npx -y ci-channel setup --repo owner/your-project
# GitLab
npx -y ci-channel setup --forge gitlab --repo group/project
# Gitea (requires GITEA_TOKEN in <project>/.claude/channels/ci/.env or the environment)
npx -y ci-channel setup --forge gitea --gitea-url https://gitea.example.com --repo owner/repoThat's it. The installer:
- Generates a webhook secret
- Provisions a smee.io relay channel
- Writes
<project-root>/.claude/channels/ci/state.json(mode0o600) - Creates (or updates) the webhook on the forge via
gh api/glab api/ Gitea REST API - Registers the
ciMCP server in<project-root>/.mcp.json - If
<project-root>/.codev/config.jsonexists, appends--dangerously-load-development-channels server:cito itsshell.architectcommand so Codev architect sessions automatically load the channel
Then launch Claude Code with the channel enabled:
claude --dangerously-load-development-channels server:ciRequirements per forge:
- GitHub:
ghCLI installed and authenticated (gh auth status) - GitLab:
glabCLI installed and authenticated (glab auth status). For self-hosted GitLab, pointglabat your instance viaglab auth loginorGITLAB_URIfirst — we don't take a--gitlab-urlflag - Gitea: a Gitea API token with
write:repositoryscope. Put it in<project-root>/.claude/channels/ci/.envasGITEA_TOKEN=...orexport GITEA_TOKEN=...in your shell before running setup. The--gitea-urlflag points at your Gitea instance.
Re-running the command is idempotent — safe to run multiple times on any forge.
Uninstall
From inside the project, with no flags:
npx -y ci-channel removeremove tears down the local install:
- Deletes
<project-root>/.claude/channels/ci/state.json - Removes the
cientry from<project-root>/.mcp.json(if the entry looks like ci-channel; customized entries are left alone with a warning) - Reverts the Codev integration in
<project-root>/.codev/config.jsonif present
remove does NOT delete the webhook on your forge — that would require re-prompting for auth and the repo. Instead, it prints the smee URL of the orphan webhook so you can delete it manually from your forge's Settings > Webhooks page if you want. The orphan webhook is harmless: it posts events to a smee URL that nobody listens to.
Works from any subdirectory of the project. No forge API calls, no auth, no flags.
LLM agents: See INSTALL.md for step-by-step installation instructions designed for AI agents to follow programmatically.
Manual Setup (advanced / GitLab / Gitea)
If you prefer to wire things up by hand — or you're installing against GitLab/Gitea — use the following two-step flow.
1. Register the MCP server in your project
cd /path/to/your-project
claude mcp add-json --scope project ci '{"command":"npx","args":["-y","ci-channel"]}'2. Start Claude Code with the channel enabled
claude --dangerously-load-development-channels server:ciOn first run, the plugin auto-generates a webhook secret and provisions a smee.io relay channel, saves them to <project-root>/.claude/channels/ci/state.json, and pushes a channel notification to Claude with the credentials you need for the next step.
3. Configure your forge webhook
Copy the URL and secret from the notification and configure your forge's webhook. For GitHub, you can use the gh CLI:
gh api repos/OWNER/REPO/hooks --method POST --input - <<'EOF'
{
"config": {
"url": "https://smee.io/YOUR_CHANNEL_URL",
"content_type": "json",
"secret": "YOUR_WEBHOOK_SECRET"
},
"events": ["workflow_run"],
"active": true
}
EOFOr configure it manually in the forge UI (see Per-Forge Setup Guides below).
That's it. No .env file to create manually, no browser visit to smee.io.
Per-Forge Setup Guides
GitHub Actions
Recommended: use the one-command installer from Quick Start.
Manual setup (run from your project directory):
claude mcp add-json --scope project ci '{"command":"npx","args":["-y","ci-channel"]}'
claude --dangerously-load-development-channels server:ciNo --forge flag needed — GitHub is the default.
Webhook configuration — Use the gh CLI or the GitHub UI:
gh api repos/OWNER/REPO/hooks --method POST --input - <<'EOF'
{
"config": {
"url": "SMEE_URL_FROM_NOTIFICATION",
"content_type": "json",
"secret": "SECRET_FROM_NOTIFICATION"
},
"events": ["workflow_run"],
"active": true
}
EOFOr manually: Settings > Webhooks > Add webhook
| Field | Value |
|-------|-------|
| Payload URL | The smee.io URL from the notification |
| Content type | application/json |
| Secret | The secret from the notification |
| Events | Select "Workflow runs" only |
.env file (<project-root>/.claude/channels/ci/.env): auto-generated on first run. If configuring manually:
WEBHOOK_SECRET=your-webhook-secretOptional CLI: Install gh CLI for startup reconciliation and failed job enrichment.
Reference: GitHub Webhooks docs, workflow_run event
GitLab CI
Recommended — one-command installer:
npx -y ci-channel setup --forge gitlab --repo group/project
claude --dangerously-load-development-channels server:ciFor nested namespaces, use the exact path_with_namespace value: --repo group/subgroup/project. Requires glab CLI installed and authenticated.
Manual setup (run from your project directory):
claude mcp add-json --scope project ci '{"command":"npx","args":["-y","ci-channel","--forge","gitlab","--repos","group/project"]}'
claude --dangerously-load-development-channels server:ciWebhook configuration — Go to your GitLab project: Settings > Webhooks > Add new webhook
| Field | Value | |-------|-------| | URL | The smee.io URL from the notification | | Secret token | The secret from the notification | | Trigger | Check Pipeline events only |
.env file (<project-root>/.claude/channels/ci/.env): auto-generated on first run. If configuring manually:
WEBHOOK_SECRET=your-gitlab-secret-tokenOptional CLI: Install glab CLI for startup reconciliation and failed job enrichment.
Reference: GitLab Webhooks docs, Pipeline events
Gitea Actions
Recommended — one-command installer (after adding GITEA_TOKEN=... to <project-root>/.claude/channels/ci/.env or exporting it in your shell):
npx -y ci-channel setup --forge gitea --gitea-url https://your-gitea-instance.com --repo owner/repo
claude --dangerously-load-development-channels server:ciThe GITEA_TOKEN must have write:repository scope — generate one at <gitea-url>/user/settings/applications.
Manual setup (run from your project directory):
claude mcp add-json --scope project ci '{"command":"npx","args":["-y","ci-channel","--forge","gitea","--gitea-url","https://your-gitea-instance.com","--repos","owner/repo"]}'
claude --dangerously-load-development-channels server:ciSecrets — Add to <project-root>/.claude/channels/ci/.env:
GITEA_TOKEN=your-gitea-api-tokenWebhook configuration — Go to your Gitea repo: Settings > Webhooks > Add Webhook > Gitea
| Field | Value | |-------|-------| | Target URL | The smee.io URL from the notification | | Secret | The secret from the notification | | Events | Select "Workflow runs" |
.env file (<project-root>/.claude/channels/ci/.env):
WEBHOOK_SECRET=your-webhook-secret
GITEA_TOKEN=your-gitea-api-tokenNote: --gitea-url is required for startup reconciliation and job enrichment (uses the Gitea API directly). GITEA_TOKEN enables authenticated API access.
Reference: Gitea Webhooks docs
Codev Projects
If you use Codev for AI-assisted development, the installer auto-wires ci-channel into your architect session. Just run it in the Codev project:
cd /path/to/your-codev-project
npx -y ci-channel setup --repo owner/your-codev-project # or --forge gitlab/giteaIf <project-root>/.codev/config.json exists, the installer appends --dangerously-load-development-channels server:ci to shell.architect (and only that field — shell.builder and all other fields are left untouched). Re-running is idempotent: if the flag is already present, the installer logs and skips.
Builders don't need the channel — they focus on implementation.
Configuration Reference
Configuration uses CLI args (passed in the args array when registering the MCP server) for structural settings, and <project-root>/.claude/channels/ci/.env for secrets. Auto-provisioned state (generated secret, smee URL) is persisted to <project-root>/.claude/channels/ci/state.json. Each project is fully isolated.
Project root detection: ci-channel walks up from process.cwd() looking for .mcp.json or .git/ to find the project root. The runtime plugin falls back to the legacy global path ~/.claude/channels/ci/ if neither is found (for backward compatibility). The ci-channel setup installer does NOT fall back — it requires a detectable project root and errors out if none is found.
Precedence: CLI args > env vars > .env file > state.json.
CLI args (structural config)
| Arg | Default | Description |
|-----|---------|-------------|
| --forge | github | Forge type: github, gitlab, or gitea |
| --repos | — | Comma-separated repo/project allowlist |
| --workflow-filter | — | Comma-separated workflow names to monitor |
| --reconcile-branches | ci,develop | Branches to check for recent failures on startup |
| --port | 0 (random) | HTTP server port (0 = OS-assigned) |
| --gitea-url | — | Gitea instance base URL (required for Gitea reconciliation) |
| --smee-url | — | smee.io channel URL (auto-provisioned if not set) |
Secrets (.env file)
| Variable | Required | Description |
|----------|----------|-------------|
| WEBHOOK_SECRET | No (auto-generated) | Shared secret for webhook signature validation |
| GITEA_TOKEN | No | Gitea API token for authenticated access |
Backward-compatible env vars
All CLI args also accept env vars for backward compatibility:
| Env var | Maps to | Notes |
|---------|---------|-------|
| FORGE | --forge | CLI arg takes precedence |
| REPOS | --repos | CLI arg takes precedence |
| GITHUB_REPOS | --repos | Legacy alias, lowest precedence |
| PORT | --port | CLI arg takes precedence |
| SMEE_URL | --smee-url | CLI arg takes precedence |
| WORKFLOW_FILTER | --workflow-filter | CLI arg takes precedence |
| RECONCILE_BRANCHES | --reconcile-branches | CLI arg takes precedence |
| GITEA_URL | --gitea-url | CLI arg takes precedence |
Example: Monitor specific repos and workflows
claude mcp add-json --scope project ci '{"command":"npx","args":["-y","ci-channel","--repos","myorg/api,myorg/frontend","--workflow-filter","CI,Deploy to Production","--reconcile-branches","main,develop"]}'
claude --dangerously-load-development-channels server:ciSmee channel management
By default, the plugin auto-provisions a smee.io channel on first run and persists it to state.json, so the same URL is reused across restarts. You only configure your forge webhook once.
To use a manually provisioned channel instead (e.g., for shared team use), include --smee-url in the args:
claude mcp add-json --scope project ci '{"command":"npx","args":["-y","ci-channel","--smee-url","https://smee.io/your-channel"]}'Features
Startup Reconciliation
When the plugin starts, it checks configured branches for recent CI failures that occurred while offline:
- GitHub:
gh run list(requires gh CLI) - GitLab:
glab ci list(requires glab CLI) - Gitea: Gitea API via
--gitea-url
Job Enrichment
After pushing the initial failure notification, the plugin asynchronously fetches the names of failed jobs. A follow-up notification with job details arrives shortly after — without blocking the initial alert.
Deduplication
Forges occasionally retry webhook delivery. The plugin tracks the last 100 delivery IDs to prevent duplicate notifications. For GitLab, a synthetic delivery ID (gitlab-{project_id}-{pipeline_id}-{status}) ensures different pipeline state transitions aren't suppressed.
Filtering
- Repository allowlist (
--repos): Only receive notifications from repos you care about - Workflow filter (
--workflow-filter): Only monitor specific workflows
Security
- Signature validation — Every webhook payload is verified:
- GitHub/Gitea: HMAC-SHA256 with timing-safe comparison
- GitLab: Token comparison with timing-safe comparison
- Localhost-only binding — The HTTP server binds to
127.0.0.1only - Repository allowlist — Optional
--reposrestricts which repos can push events - Prompt injection prevention — All user-controlled fields sanitized before inclusion in notifications
- Deduplication — Prevents replay of duplicate webhook deliveries
How to Check It's Working
Verify the MCP server is connected:
claude mcp listThe
ciserver should showConnected.Check webhook deliveries are arriving:
gh api repos/OWNER/REPO/hooks --jq '.[0].id' # Use the hook ID: gh api repos/OWNER/REPO/hooks/HOOK_ID/deliveries --jq '.[:3] | .[] | {event, status_code, delivered_at}'You should see
workflow_runevents withstatus_code: 200.Trigger a test failure: Push a commit that breaks a test. Within a minute, you should see a channel notification like:
<channel source="ci" workflow="CI" branch="main" run_url="..." conclusion="failure"> CI failure: CI on branch main — commit "break a test" by you </channel>Check the plugin logs: The plugin logs to stderr. If running locally, stderr output shows webhook receipt, signature validation, and notification delivery.
Troubleshooting
Server name vs package name
The npm package is ci-channel, but inside Claude Code it's keyed as ci in your .mcp.json. The channel flag uses the key: --dangerously-load-development-channels server:ci. Don't use server:ci-channel — it won't match.
/mcp shows ci as failed but logs say "Successfully connected"
You forgot the channel flag. The MCP server connected, but without --dangerously-load-development-channels server:ci, Claude Code doesn't treat it as a channel.
Project-scoped server appears inactive after registration
Project-scoped MCP servers are dormant until you either (a) approve them via /mcp inside a Claude Code session, or (b) add the key to enabledMcpjsonServers in ~/.claude.json. If unsure, use --scope user instead.
No notifications arriving
- Check that the plugin sent a setup notification on startup (with URL and secret)
- Verify the webhook URL and secret match between your forge and the plugin
- Confirm the correct events are enabled (Workflow runs for GitHub/Gitea, Pipeline events for GitLab)
- Verify the MCP server is registered (
claude mcp list) and you launched with--dangerously-load-development-channels server:ci
Multiple Claude sessions
The plugin defaults to port 0 (OS-assigned random port), so multiple sessions can run concurrently without port conflicts. Each session auto-provisions its own smee channel.
CLI tool errors
Startup reconciliation and job enrichment are best-effort:
- GitHub: Requires
ghCLI installed and authenticated - GitLab: Requires
glabCLI installed and authenticated - Gitea: Requires
--gitea-urlconfigured
If the CLI/API is unavailable, the plugin logs a warning and continues — live webhook notifications still work.
Development Setup
Most users should install via npx ci-channel (see Quick Start). This section is for contributors and people who want to run ci-channel from source.
git clone https://github.com/cluesmith/ci-channel.git
cd ci-channel
npm install
npm test # Run all tests (173 tests across 12 files)
npm run build # Build to dist/
npx tsx server.ts # Run from sourceTo register a source build in a project's .mcp.json:
claude mcp add-json --scope project ci '{"command":"npx","args":["tsx","server.ts"],"cwd":"/absolute/path/to/ci-channel"}'Project Structure
server.ts # MCP server entry point — HTTP, smee, bootstrap, reconciliation
lib/
forge.ts # Forge interface definition
forges/
github.ts # GitHub Actions forge implementation
gitlab.ts # GitLab CI forge implementation
gitea.ts # Gitea Actions forge implementation
config.ts # Configuration loader (CLI args + env vars + .env file)
bootstrap.ts # First-run auto-provisioning (secret, smee, notification)
handler.ts # Webhook handler pipeline (validate → dedup → filter → notify)
webhook.ts # WebhookEvent type, deduplication, filtering
notify.ts # Notification formatting and sanitization
reconcile.ts # Startup reconciliation orchestration
tests/
forges/
gitlab.test.ts # GitLab forge unit tests
gitea.test.ts # Gitea forge unit tests
webhook.test.ts # GitHub forge + shared webhook tests
notify.test.ts # Sanitization, formatting
config.test.ts # Config loading, CLI args, precedence
bootstrap.test.ts # Auto-provisioning with injected deps
reconcile.test.ts # Reconciliation, job enrichment
integration.test.ts # GitHub HTTP pipeline end-to-end
integration-gitlab.test.ts # GitLab HTTP pipeline end-to-end
integration-gitea.test.ts # Gitea HTTP pipeline end-to-end
stdio-lifecycle.test.ts # MCP stdio stability regression test
fixtures/ # Sample webhook payloads per forgeContributing
This is a Codev project and follows the Codev methodology for AI-assisted development. The preferred way to contribute is via PRs generated with Codev — three documents in addition to the code changes.
Each feature follows the three-document model:
- Specification (
codev/specs/) — What to build and why - Plan (
codev/plans/) — How to build it, in testable phases - Review (
codev/reviews/) — What was learned, deviations from plan
PRs that include Codev artifacts (spec, plan, review) are significantly easier to review and integrate.
The project's architectural knowledge lives in:
- Architecture — System design, data flow, security model, component reference
- Lessons Learned — Accumulated insights from reviews and production use
See CLAUDE.md for Claude Code-specific instructions and AGENTS.md for cross-tool AI agent compatibility (Cursor, GitHub Copilot, etc.).
License
MIT License. See LICENSE for details.
