agent-comms
SendMessage recipient validation and worktreePath safety (CWE-59). TRIGGER when: validating a SendMessage `to:` recipient against the agent whitelist, or a…
Correct construction of watchers for long-running operations. TRIGGER when: arming observation of a long-running operation (CI run, deploy, transfer, GC/prune, log stream), writing poll/until loops, or using the Monitor tool. SKIP: defining production alerts/metrics (use
$ npx -y skills add komluk/scaffolding --skill watch-patterns --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/watch-patternsContext preview
The summary Claude sees to decide when to auto-load this skill.
Correct construction of watchers for long-running operations. TRIGGER when: arming observation of a long-running operation (CI run, deploy, transfer, GC/prune, log stream), writing poll/until loops, or using the Monitor tool. SKIP: defining production alerts/metrics (use
name: watch-patterns description: "Correct construction of watchers for long-running operations. TRIGGER when: arming observation of a long-running operation (CI run, deploy, transfer, GC/prune, log stream), writing poll/until loops, or using the Monitor tool. SKIP: defining production alerts/metrics (use monitoring-observability); log formatting (use logging-standards)."
How to observe long-running operations correctly. Anyone can run a loop — the value is a watcher that **never lies**: it terminates, it detects failure as reliably as success, and its silence is never mistaken for progress.
Key architectural fact: a subagent cannot hold a long-lived observation. Notifications from an armed `Monitor` land in the conversation that armed it; a subagent finishes and dies. Observation is therefore always armed by the main loop — this skill is loadable anywhere for exactly that reason.
---
The single most common failure is picking the wrong mechanism. Decide BEFORE writing any script:
| Situation | Mechanism | |-----------|-----------| | "Is it done *now*?" — answerable immediately | **One-shot check.** Single command, no loop, no monitor. | | ONE notification when a condition becomes true (deploy finished, download complete, GC done) | **`Bash(run_in_background)` + `until` loop** with a terminal condition and a bounded iteration count. NOT `Monitor`. | | REPEATING events, each occurrence matters (every error line, every restart) | **`Monitor` tool** with a filter covering ALL terminal states. | | CI run status (Gitea Actions) | **MCP polling**: `mcp__gitea__actions_run_read` polled until `status` is terminal. Structured status beats scraping logs. | | Metric/log condition (error rate, service down) | **MCP**: `mcp__grafana__query_prometheus` / `query_loki_logs`. One query returns aggregated truth; a bash loop re-derives it badly. | | You only need the outcome eventually, no urgency | **Do nothing now.** Check once when the result is actually needed. |
Unbounded `tail -f` / `while true` piped into `Monitor` when only ONE notification was needed → the tool stays armed until timeout, and every intermediate line risks a spurious notification. Single notification = `Bash(run_in_background)` + `until`; `Monitor` is for recurring events only.
# Bash(run_in_background): fires ONCE, always terminates
for i in $(seq 1 120); do # bound: 120 × 30s = 1h hard ceiling
STATUS=$(remote_status_cmd 2>&1) || STATUS="PROBE_FAILED"
case "$STATUS" in
*done*|*success*) echo "RESULT: success — $STATUS"; exit 0 ;;
*failed*|*error*) echo "RESULT: failed — $STATUS"; exit 1 ;;
esac
sleep 30
done
echo "RESULT: timeout after 1h — last status: $STATUS"; exit 2Every branch — success, failure, probe error, timeout — produces output. There is no code path that ends in silence.
---
A filter that only matches the happy path makes a crash indistinguishable from "still running". Before arming ANY watcher, ask:
> **"If this process died right now, would my filter emit anything?"**
If the answer is no, the watcher is broken — fix it before arming.
| Filter | Verdict | |--------|---------| | `grep "success"` | BAD — crash = silence | | `grep -E "success\|complete"` | BAD — still only happy path | | `grep -E "success\|complete\|fail\|error\|fatal\|panic\|denied\|timeout"` | GOOD — alternation covers terminal states | | `until ! kill -0 "$PID" 2>/dev/null; do sleep 1; done; echo "exited rc=$(cat rcfile 2>/dev/null)"` | GOOD — process disappearance IS the event |
Rules:
(exit code, PID disappearance, API `status` field). Logs lie by omission; exit states don't.
(`FAILED`, `ERROR`, `fatal:`, `Traceback`, `panicked`, `unreachable`) — read a sample of real output first to learn it.
operation is expected to finish. Timeout is a terminal state too — emit it explicitly. Unbounded loops convert "it never finished" into silence.
---
| Concern | Rule | |---------|------| | Buffering | Pipelines swallow lines: use `grep --line-buffered`, `awk '{print; fflush()}'`, `stdbuf -oL` for stubborn tools. NEVER put `head -N` mid-stream — it SIGPIPEs the producer and kills the pipeline early. | | Stderr | Always `2>&1` on commands you spawn — crashes print to stderr, and a filter reading only stdout misses them. | | Transient errors | A remote probe MAY fail transiently: `OUT=$(curl -fsS "$URL" 2>&1) || OUT="PROBE_FAILED: $OUT"` — capture and classify; a blind `\|\| true` on every command converts persistent outage into silence. Count consecutive failures; N in a row = terminal state "target unreachable". | | Intervals | Remote APIs / SSH probes: **≥ 30 s**. Local files/processes: 0.5–1 s. Hammering a remote API is both rude and a great way to get rate-limited into false "failures". | | Idempotent probes | Each iteration must be self-contained. Do not cache a mount path, connection, or PID across iterations — re-resolve it (see autofs warning below). |
---
**IP addresses in the model's context window may be MASKED (`<PRIVATE_IP>`).** You cannot copy an IP from context into a script — you would arm a watcher pointed at a placeholder or an empty string. Observed failure: `$NAS` was empty → `/dev/tcp//22: Invalid argument` — the script "r
Spec-driven multi-agent orchestration for Claude Code — pure markdown, zero backend, runs on the stock runtime. 13 agents, 36 skills, 19 commands, 15 hooks, per-phase model tiers, opt-in lifecycle hooks, optional cross-device semantic memory.
Repo: komluk/scaffolding
SendMessage recipient validation and worktreePath safety (CWE-59). TRIGGER when: validating a SendMessage `to:` recipient against the agent whitelist, or a…
3-tier markdown memory protocol (shared/agent/conversation) for cross-session knowledge. TRIGGER when: reading or writing agent memory files, choosing which…
RESTful API design standards: resource naming, HTTP methods, status codes, pagination, versioning. TRIGGER when: designing new API endpoints, defining error…
Optimize Claude Code context-window usage for accuracy and cost. TRIGGER when: hitting context limits, structuring prompts for an agent, or trimming what gets…
Schema design, index strategy, migration safety, and query analysis. TRIGGER when: designing tables or indexes, writing a migration, or diagnosing a slow…