.claude/skills/polling-loop-finalization-escape/SKILL.md
Fix polling loops that repeat finalization actions instead of exiting after a trigger. Use when: (1) a return/completion signal is detected but the loop keeps running and re-triggering the same action, (2) "resilient" try/except blocks inside a polling loop cause the loop to continue after a finalization error, (3) repeated dock/stop/ cleanup commands are sent when only one was expected. The pattern: inner finalization steps are individually try/excepted for resilience, but an outer except catches unexpected errors and doesn't exit, causing the loop to re-detect the trigger.
npx skillsauth add Dbochman/dotfiles polling-loop-finalization-escapeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A polling loop detects a trigger condition (e.g., "return home detected"), executes finalization steps (dock Roombas, send notification, update state), then should exit. But if any exception occurs during finalization that isn't caught by the inner try/except blocks, the outer loop's generic except Exception handler catches it, logs the error, and continues the loop — causing the trigger to be re-detected and finalization to re-execute on every poll cycle.
if trigger_condition: ... return finalization blockexcept Exception around the entire poll iteration that logs but doesn't returnEnsure the outer exception handler checks whether the trigger was already detected, and if so, exits instead of looping:
# BEFORE (broken): exception during finalization causes loop to continue
while polling:
try:
if detect_trigger():
try:
finalize_step_1()
except Exception:
log("step 1 failed (non-fatal)")
try:
finalize_step_2() # <-- unexpected error here
except Exception:
log("step 2 failed (non-fatal)")
return # never reached if step_2 throws something uncaught
except Exception as e:
log(f"Error: {e}") # catches it, but doesn't exit!
await sleep(interval) # loops back and re-triggers
# AFTER (fixed): outer handler respects the trigger state
while polling:
trigger = None
try:
trigger = detect_trigger()
if trigger:
# ... finalization steps ...
return
except Exception as e:
log(f"Error: {e}")
if trigger:
log("Exiting despite error (trigger was already detected)")
return # don't loop back and re-trigger
await sleep(interval)
After fix, logs should show exactly one "trigger detected" + "docking" sequence, then the monitor ends. No repeated dock commands at 30s intervals.
UnboundLocalError on a variable that appears unreachable) — fixing the exception alone isn't sufficient because other future exceptions could cause the same loop behaviortools
Use exact configured Reolink cameras through the local Home Hub for availability and power status, fresh stills, visual commentary, protected Dylan/Julia/household sharing, and reversible spotlight control. Supports trusted owner tasks and explicitly scoped proactive automations; not for Nest or Ring cameras, arbitrary recipients, recordings, account changes, or raw camera APIs.
data-ai
Privately manage Dylan and Julia's household plant inventory and care history by physical location, bed, and exact Flower Cam view. Use for confirmed plant onboarding from camera conversations, camera- or bed-filtered inventory, record corrections, individual or whole-bed care, and private filtered exports. Pair with reolink-camera when an owner asks about plants visible in Flower Cam images.
testing
Inspect and control the physically secured Reachy Mini at Crosstown through ClawBody. Use for requests to check Reachy, look around, express an emotion, play any official emotion or dance preset, speak proactively, mute or unmute its microphone, stop movement, or describe what its camera sees.
tools
Handle Reachy/iMessage handoffs, selective durable memory, forgetting, and diagnostics; automatic context comes from the gateway plugin.