Adds a periodic timer (default 30s) that calls drain() unconditionally, covering the case where chokidar/inotify silently drops an IN_MODIFY event. Observed twice in production: ping appended to inbox, file mtime updated, but no event delivered to the watcher; a sibling-file touch unblocked it. Root cause is Linux inotify under brief idle gaps + atomic writes — not consistently reliable on its own. drain() is already idempotent (HWM comparison short-circuits when nothing's new), so the steady-state overhead is one stat + JSON parse per poll cycle. Event-driven path remains the primary; the poll just masks the rare miss within the cycle interval. - safetyPollMs option: default 30_000, set to 0 to disable - stop() clears the interval before closing chokidar - Two new tests: safety-poll delivers when fs-event never fires; safetyPollMs:0 truly disables the timer Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| src | ||
| test | ||
| .gitignore | ||
| install.sh | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| vitest.config.ts | ||
agent-watcher-mcp (Layer 2)
The MCP Watcher: a Claude Code stdio MCP server that surfaces ping-inbox
events into your Claude Code session via Channels, and exposes reply tools
(ack / respond / mark_handled) so Claude can react.
Spec: ../spec/agent-watcher.md §4.
What it does
- Resolves identity (env /
$CLAUDE_HOME/ping-agent/~/.ping-agent). - Writes
pings/.<agent>.watcher-activeso the existing agent-pingUserPromptSubmithook stands down on this host. - Watches
pings/<agent>.inboxvia inotify (chokidar). - On change, drains unread pings (since HWM), applies sentinel
deferral (warn-after-3), and emits each as a
notifications/claude/channelevent. - Exposes three MCP tools so Claude can ack, respond, or mark a ping handled.
- Removes the sentinel on graceful shutdown.
Install
Per CLAUDE.md rule #2, the agent does not install on itself. A human runs:
cd /path/to/agent-watcher/mcp-watcher
./install.sh
This installs deps, builds, symlinks the binary into ~/.local/bin/,
adds .stignore patterns, and prints the mcp.json snippet to paste
into Claude Code's config.
Launch
Channels is in research preview. Start Claude Code with:
claude --dangerously-load-development-channels server:agent-watcher
Sandbox testing
To run a separate Claude Code session with a different identity (so it
won't compete with the default ~/.ping-agent for inbox reads):
mkdir -p ~/.claude-sandbox
echo sandbox > ~/.claude-sandbox/ping-agent
CLAUDE_CONFIG_DIR=~/.claude-sandbox \
claude --dangerously-load-development-channels server:agent-watcher
The sandbox session reads pings/sandbox.inbox and writes
pings/.sandbox.watcher-active — fully isolated from prod.
How it interacts with agent-ping
The agent-ping UserPromptSubmit hook checks for the sentinel file
pings/.<agent>.watcher-active at startup. If the file is present and
the PID inside is alive, the hook stands down — the watcher is the
delivery primitive. If the watcher exits (graceful or crash with stale
sentinel cleared by the hook's age check), the hook resumes
async-on-next-prompt delivery.
This means you can run with the watcher OR the hook OR both configured — the sentinel arbitrates.
Tests
npm test # unit tests via vitest (35 currently passing)
npm run typecheck # tsc --noEmit
npm run build # tsc → dist/
Observability
- Logs to stderr (visible via
claude --debugor the Claude Code MCP debug log at~/.claude/debug/<session-id>.txt). - Sentinel file content includes PID + start time for the hook's age/liveness check.
Limitations / v2
- Sentinel hook coexistence requires the agent-ping hook to know how
to read the sentinel. PR pending against
agent-pingto add the check. - No reconnection / restart on Claude Code session restart — Claude Code spawns the subprocess anew each session, so the watcher re-drains from HWM cleanly.
- One watcher process per agent identity. Two sessions with the same
identity contending on the same inbox is undefined behaviour
(use
CLAUDE_HOMEto scope identities). - Channels is research preview; if the API changes, expect to update the meta-key sanitization or notification shape.