--- name: session-inbox-monitor version: 0.2.1 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 - `/.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: `), 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\\\\.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. - **Mojibake on force-delivery** → the Stop-hook (`stop-dispatcher.ps1`) injects message bodies to stdout; WinPS 5.1 must set `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8` or non-ASCII (Cyrillic) bodies arrive mangled (it emits in the OEM code page under a harness-spawned redirected pipe). Inbox messages must be written as **no-BOM UTF-8, LF** — the Write tool does this; PowerShell writers must use `[IO.File]::WriteAllText($p,$t,[Text.UTF8Encoding]::new($false))`, NOT `Set-Content`/`Out-File -Encoding utf8` (which adds a BOM under 5.1). Same WinPS-5.1 encoding class as the hook-source em-dash gotcha. Fixed + in-situ verified 2026-06-17. ## 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.