Skip to content

mividtim/claude-code-event-listeners

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

claude-code-event-listeners

Event-driven background task listeners for Claude Code. Replace polling with real event notifications.

Demo

The Problem

Claude Code uses a request-response model. When waiting for CI, log output, webhooks, or file changes, the typical approach is polling: sleep, check, repeat. This burns turns, wastes tokens, and feels janky.

The Insight

Claude Code's background task mechanism (run_in_background: true) provides genuine event-driven behavior:

  1. A background task runs a blocking command that waits for an event
  2. While waiting, Claude does nothing — no turns burned, no tokens spent
  3. When the event occurs, the command exits and Claude gets a <task-notification>
  4. Claude reads the output, reacts, and optionally re-subscribes

This isn't polling dressed up. The OS does the blocking. Claude only wakes up when something actually happens.

                    ┌──────────────────────────┐
                    │  Background Task          │
                    │  (event source script)    │
                    │                           │
  Event source ───► │  Blocks until event ───►  │ ──► Task completes
  (log, webhook,    │  Outputs event data       │      ↓
   CI, file, ...)   │  Exits cleanly            │     Claude gets notified
                    └──────────────────────────┘      reads output, reacts,
                                                      starts new listener

Install

# From the marketplace
claude plugin marketplace add mividtim/claude-code-event-listeners
claude plugin install el

# Or load directly for a single session
claude --plugin-dir /path/to/claude-code-event-listeners

Slash Commands

Event Sources

Command What it does
/el:log-tail <file> [timeout] [max_lines] Tail a log file, return chunks of output
/el:webhook [port] One-shot HTTP server on localhost
/el:webhook-public [port] [name] [subdomain] One-shot HTTP server with ngrok tunnel (stable vanity URL with subdomain)
/el:ci-watch <run-id | branch> Watch a GitHub Actions run until completion
/el:pr-checks <pr-number> Watch all PR checks until they resolve
/el:file-change [--root dir] <path-or-glob>... Watch file(s) for modifications (supports globs)
/el:context-sync [project-root] Watch CLAUDE.md and .claude/ docs for cross-session sync
/el:poll <interval> <command> Poll a command, fire when output changes
/el:listen <command...> Run any blocking command as an event source

Management

Command What it does
/el:list Show all available sources (built-in + user)
/el:register <script> Register a custom event source
/el:unregister <name> Remove a user-installed source

Quick Start

Tail a log file

You: /el:log-tail api.log
... time passes, log lines arrive ...
<task-notification> → Claude reads the chunk, summarizes errors/warnings
Claude: starts another listener for the next chunk

Wait for CI

You: /el:ci-watch my-branch
... minutes pass, Claude does nothing ...
<task-notification> → CI passed! (or failed → Claude investigates)

Monitor an API

You: /el:poll 60 "curl -s https://api.example.com/status | jq .count"
... polls every 60s, Claude does nothing ...
<task-notification> → count changed! Claude reads old/new value and reacts

Receive a webhook

You: /el:webhook-public 9999 gh-review
Claude: immediately reads URL: WEBHOOK_URL=https://xxxx.ngrok.app
         registers URL with GitHub
... waits ...
<task-notification> → GitHub POSTed a review event → Claude reads and reacts

Architecture: Pluggable Event Sources

The plugin is designed as a platform, not a monolith. Every event source — including the built-in ones — is a standalone script in sources.d/.

event-listen.sh (dispatcher)
    │
    ├── looks up source type in:
    │   1. ~/.config/claude-event-listeners/sources.d/  (user, wins)
    │   2. <plugin>/sources.d/                          (built-in)
    │   3. ~/.claude/plugins/cache/*/*/sources.d/        (community plugins)
    │
    └── exec's the matching script with remaining args

Built-in sources are not special. They can be overridden, replaced, or used as templates for new ones.

The Event Source Protocol

An event source is any executable script that:

  1. Receives args as $@
  2. Blocks until an event occurs
  3. Outputs event data to stdout
  4. Exits cleanly

That's the entire contract. Here's a minimal example:

#!/bin/bash
# sources.d/port-ready.sh — Wait for a TCP port to open.
# Args: <host> <port>
set -euo pipefail
HOST="${1:?}" PORT="${2:?}"
while ! nc -z "$HOST" "$PORT" 2>/dev/null; do sleep 1; done
echo "PORT_OPEN=$HOST:$PORT"

Managing Sources

/el:list                              # List all sources
/el:register ./my-custom-source.sh    # Register a new source
/el:unregister my-custom-source       # Remove a user source

User sources override built-ins with the same name — so you can replace log-tail with your own implementation by registering a script named log-tail.sh.

Creating Community Event Sources

Write your script following the protocol. Publish it as a Claude Code plugin:

  1. Create a repo named claude-code-el-<source-name>
  2. Put your source script(s) in sources.d/
  3. Add .claude-plugin/plugin.json declaring el as a dependency
  4. Users install via the marketplace — el auto-discovers the source
# Users install your source as a plugin:
claude plugin marketplace add yourname/claude-code-el-my-source
claude plugin install el-my-source
# That's it — el discovers sources.d/ automatically

Sources can also be registered manually without the marketplace:

git clone https://github.com/you/claude-code-el-my-source.git
/el:register ./claude-code-el-my-source/sources.d/my-source.sh

Community Sources

Source What it does Repo
http-poll Poll a URL until status/body matches claude-code-el-http-poll
coderabbit Poll for new CodeRabbit reviews/comments on a PR claude-code-el-coderabbit
slack Listen for Slack messages via webhook claude-code-el-slack
sendgrid Receive inbound emails via SendGrid Inbound Parse claude-code-el-sendgrid

Want to build one? We'd love to see:

  • postgres-changes — LISTEN/NOTIFY on a Postgres channel
  • docker-health — Wait for a container health check to pass/fail
  • redis-subscribe — Subscribe to a Redis pub/sub channel
  • mqtt-subscribe — Subscribe to an MQTT topic
  • s3-object — Wait for an S3 object to appear

Name your repo claude-code-el-<source-name> and open a PR to add it to the table above.

Requirements

  • bash (3.2+ for macOS, 4.0+ for Linux)
  • python3 (for webhook sources)
  • gh CLI (for ci-watch and pr-checks) — install
  • ngrok (for webhook-public only) — install

Contributing

The best way to contribute is to write new event sources. See the Event Source Protocol above and the scripts in sources.d/ for examples.

License

MIT

About

Event-driven background task listeners for Claude Code. Replace polling with real event notifications for logs, webhooks, CI, and file changes.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors