Files
claude-skills/skills/session-inbox-monitor/SKILL.md
vitya c3e1ce7b40 feat(session-inbox-monitor): SessionStart hook + fill SKILL body v0.2.0
Core content task of the session-inbox-monitor line. Two deliverables:

1. SessionStart hook `skills/session-inbox-monitor/hooks/inbox-monitor.ps1`
   (versioned for multi-machine rollout; deployed to ~/.claude/hooks/ and
   registered in ~/.claude/settings.json SessionStart):
   - sweep: Get-CimInstance | Stop-Process orphaned monitors of THIS inbox,
     matched by sentinel CLAUDE_INBOX_MONITOR + inbox path (a /clear leaves
     the poll process alive -> re-raise without sweep stacks duplicates);
   - inject: hookSpecificOutput.additionalContext with the exact persistent
     Monitor command (Monitor tool, not background Bash);
   - opt-in gate: fires only on .claude-inbox/ dir or CLAUDE.md trigger line.
   ASCII-only (em-dash -> mojibake under WinPS 5.1 without BOM, fixed).

2. SKILL.md body filled (When to use / Inputs / Steps / Deployment /
   Failure modes / Side effects / What NOT to do); bump 0.1.0 -> 0.2.0 MINOR.

Headless: no hook-level signal exists (verified via claude-code-guide) ->
agent-side best-effort skip, default errs toward raising (false-skip in
interactive loses the feature; false-raise in headless is a harmless no-op).

Live-verified: inject -> valid JSON; sweep -> killed a planted orphan (PASS);
real Monitor tool spawns a bash process carrying the sentinel (sweep will
find real orphans); settings.json stays valid. Sweep over-match edge and
multi-session-per-project limit documented honestly in Failure modes.

Closes [session-inbox-monitor-sessionstart-hook].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:48:50 +03:00

128 lines
6.9 KiB
Markdown

---
name: session-inbox-monitor
version: 0.2.0
description: >
Raises a persistent Monitor (Monitor tool, NOT background Bash) on the
project's `.claude-inbox/` at the start of an interactive session, so
inter-session messages page the session in real time; the monitor dies on
session end on its own. A paired SessionStart hook injects the
raise-instruction and first sweeps orphaned monitors of this inbox (a
`/clear` leaves them running → re-raise would stack duplicates). Triggers:
CLAUDE.md line `inbox monitor: raise on start`, or «подними монитор почты»,
«настрой авто-монитор инбокса», «raise inbox monitor», «auto-arm inbox
watcher». Headless (`claude -p`): does NOT raise — Monitor doesn't work
there; rely on the Stop-hook inbox pickup + Notify/ntfy. NOT for how to
handle a received message (→ inter-session-peer-discipline) nor the
multi-machine inbox backend (→ cross-machine-inbox design).
---
# session-inbox-monitor
Auto-raises a session-length Monitor on `.claude-inbox/` at interactive-session
start (via a paired SessionStart hook that injects the instruction and sweeps
orphans), so inter-session messages page the session in real time. Tears down
for free on session end. Headless sessions skip it and rely on the pull-model
(Stop-hook pickup + Notify).
## When to use
- **Automatic (the common path).** The paired SessionStart hook injects an
instruction at the start of every interactive session of an opted-in project.
You act on that injection — raise the monitor as your first action — without a
user phrase.
- **On request.** CLAUDE.md line `inbox monitor: raise on start`, or «подними
монитор почты», «настрой авто-монитор инбокса», «raise inbox monitor»,
«auto-arm inbox watcher».
- **NOT for** handling the content of a received message (→
`inter-session-peer-discipline`), nor the multi-machine delivery backend (→
`cross-machine-inbox`). This skill is only the monitor's *lifecycle* on one
machine.
## Inputs
- `<project>/.claude-inbox/` — the watched directory. Direct-child `*.md` files
are inbox messages (the Stop-hook moves them to `.read/` once handled).
- The SessionStart hook supplies the **exact Monitor command** to run, with the
sweep sentinel (`CLAUDE_INBOX_MONITOR`) and the absolute inbox path baked in.
Use it verbatim — do not hand-author a different poll command, or the sweep
won't recognise the process it spawns.
## Steps
1. **Mode check.** If this is a headless / non-interactive run (`claude -p`),
**STOP — do not raise a monitor.** The Stop-hook inbox pickup plus `Notify:`/
ntfy cover delivery there; a Monitor can't idle-watch in headless and is
killed ~5s after the run. There is no hook-level headless signal, so this is
your judgement call from the run context.
2. **Raise exactly one persistent Monitor** using the command the hook injected:
the **Monitor tool** with `persistent: true`, `description: "inbox watcher"`.
The hook already swept any orphan before injecting, so you start from a clean
slate — raise one, not more.
3. **Do not sweep yourself.** Killing orphans is the hook's job (it runs before
you, at SessionStart, when no other session activity is live).
4. **On an event** (`New inter-session message in inbox: <name>`), read
`.claude-inbox/` and handle the message per `inter-session-peer-discipline`.
The Stop-hook also force-delivers any inbox messages at end of turn as a
backstop, so nothing is lost if the monitor missed a beat.
5. **Teardown is automatic.** The Monitor dies at session end. Do **not** add a
SessionEnd teardown — and note `/clear` does not fire SessionEnd anyway
(that's why the sweep lives in SessionStart, not SessionEnd).
## Deployment (machine-local)
- Hook script: `skills/session-inbox-monitor/hooks/inbox-monitor.ps1` (versioned
here) → deploy to `~/.claude/hooks/inbox-monitor.ps1`.
- Register in `~/.claude/settings.json` under `hooks.SessionStart` (no matcher →
fires on startup/resume/clear/compact), e.g.:
```json
{ "hooks": [ { "type": "command",
"command": "powershell -NoProfile -ExecutionPolicy Bypass -File \"C:\\Users\\<you>\\.claude\\hooks\\inbox-monitor.ps1\"",
"timeout": 15, "statusMessage": "inbox-monitor" } ] }
```
- Twin pattern: `poller-interactive-lock-writer` (`interactive-lock.ps1`).
- Opt-in per project: the hook fires only when the project has a `.claude-inbox/`
directory **or** a CLAUDE.md line `inbox monitor: raise on start`.
## Failure modes
- **No inbox, no opt-in line** → the hook injects nothing; no monitor. Expected
for projects that don't use inter-session messaging.
- **Two live interactive sessions on the same project** → the second session's
SessionStart sweep kills the first session's monitor (the match is
per-inbox-path, not per-session). Known limitation; the deliberate invariant is
"exactly one monitor per inbox per machine." If the first session is still
active, its next Stop-hook turn still delivers inbox mail — only the real-time
paging is lost until it re-raises. See `inter-session-peer-discipline`.
- **Sweep over-match** → any *live* process whose command line contains both the
sentinel `CLAUDE_INBOX_MONITOR` and the inbox path is killed. At a real
SessionStart no agent/tool processes are running yet, so only the orphaned
monitor matches. Don't echo or run a command carrying that sentinel+path during
a session's startup.
- **Headless didn't skip** → a monitor raised in headless is a harmless no-op,
killed ~5s after the run ends. The default errs toward raising because a
false-skip in an interactive session would silently lose the feature.
- **Monitor auto-stopped** → the harness stops monitors that emit too many
events. The injected poll command de-dups by filename (pages once per message,
not every 15s) to stay under that bar.
## Side effects
- Spawns one Monitor (and its backing Git-Bash poll process) per interactive
session; both die at session end.
- Force-kills orphaned monitor processes of this inbox at every SessionStart.
- **No repo writes.** The hook and its `~/.claude/settings.json` registration are
machine-local; only this skill (docs) and `.claude-inbox/` activity are in play.
## What NOT to do
- **Don't watch the inbox with a background Bash** (`run_in_background`) — it
leaks across `/clear` and accumulates zombies. Use the Monitor tool.
- **Don't add a SessionEnd teardown hook** — the Monitor self-terminates, and
`/clear` never fires SessionEnd.
- **Don't raise more than one monitor.** The hook guarantees a clean slate before
you raise.
- **Don't handle message content here** — that's `inter-session-peer-discipline`.
- **Don't rely on this in headless** — use the pull model (Stop-hook + Notify).
Active headless polling, if ever needed, is a separate cron Routine, not this
skill.