Polling Guard — Monitoring Loop Fidelity
Problem
During Rune multi-agent workflows, the LLM orchestrator frequently improvises Bash("sleep 60 && echo poll check") instead of following the waitForCompletion pseudocode that requires calling TaskList on every poll cycle. This anti-pattern:
- Provides zero visibility into task progress (no TaskList call = no status check)
- Uses wrong intervals (45s, 60s instead of configured 30s)
- Wastes tokens and time — sleeping without checking means missed completions
- Persists despite text warnings — instruction drift after 20+ turns makes text-only rules unreliable
The Rule: Correct vs Incorrect Monitoring
CORRECT — TaskList on every cycle
TaskList() <- MANDATORY: check actual task status
count completed
log progress
check if all done
check stale tasks
Bash("sleep ${pollIntervalMs/1000}", { run_in_background: true }) <- MUST use run_in_background for sleeps >= 2s (harness blocks standalone sleep >= 2s)
INCORRECT — sleep+echo proxy
Bash("sleep 60 && echo poll check") <- BLOCKED: skips TaskList entirely
Canonical Monitoring Loop
This is the 6-step inline template. Every waitForCompletion call MUST translate to this pattern:
POLL_INTERVAL = pollIntervalMs / 1000 // derive from per-command config (seconds)
MAX_ITERATIONS = ceil(timeoutMs / pollIntervalMs)
for iteration in 1..MAX_ITERATIONS:
1. Call TaskList tool <- MANDATORY every cycle
2. Count completed vs expectedCount
3. Log: "Progress: {completed}/{expectedCount} tasks"
4. If completed >= expectedCount -> break
5. Check stale: any task in_progress > staleWarnMs -> warn
6. Call Bash("sleep ${POLL_INTERVAL}", { run_in_background: true }) <- MUST use run_in_background (harness blocks sleep >= 2s)
Parameters are derived from per-command config — never invented:
maxIterations = ceil(timeoutMs / pollIntervalMs)sleepSeconds = pollIntervalMs / 1000
See monitor-utility.md for the full utility specification and per-command configuration table.
Classification Checklist
| Context | Action |
|---|---|
Bash("sleep 30", { run_in_background: true }) after TaskList call |
CORRECT — monitoring cycle |
Bash("sleep 30") without run_in_background |
BLOCKED by harness — standalone sleep >= 2s is rejected |
Bash("sleep N && echo ...") |
BLOCKED — anti-pattern (hook will deny) |
Bash("sleep N; echo ...") |
BLOCKED — semicolon variant also caught |
Bash("sleep ${DELAY}") in retry loop |
LEGITIMATE — retry backoff, not monitoring |
sleep(pollIntervalMs) in pseudocode |
CORRECT — reference to config value |
Anti-Patterns — NEVER DO
Bash("sleep N")withoutrun_in_background: true— Claude Code harness blocks standalonesleep Nwhere N >= 2 seconds. Always useBash("sleep N", { run_in_background: true }).Bash("sleep N && echo poll check")— blocks TaskList, provides zero visibility into task progress. This is the canonical anti-pattern. Also caught by POLL-001 hook.Bash("sleep N; echo poll check")— semicolon variant, same anti-pattern. Caught by enforcement hook.Bash("sleep 45")orBash("sleep 60")— wrong interval. Config says 30s (pollIntervalMs: 30_000). Derive from config, don't invent.- Monitoring loop without TaskList call — sleeping without checking means you cannot detect completed tasks or stale workers.
- Arbitrary iteration counts — must derive from
ceil(timeoutMs / pollIntervalMs). Don't hardcode10or20iterations.
Enforcement
The enforce-polling.sh PreToolUse hook blocks sleep+echo anti-patterns at runtime during active Rune workflows. Deny code: POLL-001.
- Detection:
sleep N {&&|;} echo/printfwhere N >= 10 seconds - Scope: Only during active workflows (arc checkpoints or
.rune-*state files — covers review, audit, work, mend, plan, forge, inspect, goldmask) - Recovery: If POLL-001 fires, switch to the canonical monitoring loop above
If this skill is loaded correctly, the hook should rarely fire — the skill teaches the correct pattern before mistakes happen. The hook catches failures as a safety net.
Additional Patterns
For advanced waiting patterns beyond TaskList polling (condition-based waiting, exponential backoff, deadlock detection), see condition-based-waiting.md.
Reference
- monitor-utility.md — full monitoring utility specification, per-command config table, and Phase 2 event-driven fast path
- CLAUDE.md Rule #9 — inline polling fidelity rule
pollIntervalMsis sourced from the per-command config table (don't hardcode 30s if config changes)
Converted and distributed by TomeVault — claim your Tome and manage your conversions.