autoloop: Stop
Cleanly terminate a self-revising loop. Appends a ## DONE section with timestamp + reason, sends a PushNotification summarizing final state, and stops scheduling new wake-ups. The next /loop firing will see the DONE marker and exit without acting.
Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
Arguments
- Positional (optional): reason string. Defaults to "user-requested stop".
--keep-forensics(flag, optional): retain the state directory after stop instead of archiving + removing it. Default behavior since Wave 2: cleanup tarball is created and<state_dir>isrm -rf'd. Pass this flag if you need to inspect heartbeat.json or revision-log/ after stop.
Step 1: Locate contract
CONTRACT_PATH="${CONTRACT_PATH:-./LOOP_CONTRACT.md}"
if [ ! -f "$CONTRACT_PATH" ]; then
echo "No contract at $CONTRACT_PATH — nothing to stop."
exit 0
fi
If the user hasn't specified which contract, and multiple LOOP_CONTRACT.md files exist under the cwd, use AskUserQuestion to pick.
Step 2: Confirm stop reason
Use AskUserQuestion to pick a stop reason:
Research saturation— 3 consecutive null-rescue firingsGoal achieved— completion criterion metUser request— manual terminationBlocked on external dependency— can't proceed without intervention
Record the chosen reason plus the free-text user note (if provided) into the DONE section.
Step 3: Append DONE section
Append this block to the contract (use Edit or a Bash cat >>):
---
## DONE
- **Stopped at**: <ISO 8601 UTC>
- **Iteration**: <current value from frontmatter>
- **Reason**: <chosen reason>
- **User note**: <free-text if any>
- **Final state summary**:
<one-paragraph synthesis of the Current State section at time of stop>
The next /loop firing will observe this section and exit without further action.
Step 4: Send PushNotification
Load PushNotification via ToolSearch if not already available, then send:
<loop-name> stopped at iter <N>. Reason: <reason>. Final state: <one-line summary>.
Keep under 200 chars.
Step 5: Unload launchd plist
Before unregistering, unload the launchd plist:
# Source the launchd library
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}"
source "$PLUGIN_ROOT/scripts/launchd-lib.sh"
source "$PLUGIN_ROOT/scripts/state-lib.sh"
# Derive loop_id and state_dir
loop_id=$(derive_loop_id "$CONTRACT_PATH")
state_dir=$(state_dir_path "$loop_id" "$CONTRACT_PATH")
# Unload plist (idempotent; no-op on non-macOS)
if ! unload_plist "$loop_id" "$state_dir" 2>/dev/null; then
echo "WARNING: Failed to unload launchd plist" >&2
fi
Step 6: Unregister loop from machine registry
After unloading the plist, clean up the machine registry entry:
# Source the registry library
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}"
source "$PLUGIN_ROOT/scripts/registry-lib.sh"
# Derive loop_id from contract path
loop_id=$(derive_loop_id "$CONTRACT_PATH")
# Unregister from machine registry (idempotent; no error if already absent)
if ! unregister_loop "$loop_id"; then
echo "WARNING: Failed to unregister loop from machine registry" >&2
fi
Step 7: Clean state directory (Wave 2)
After unregistering, archive and remove the loop's state directory so it doesn't accumulate as an orphan. The tarball is created next to the dir (in its parent), then the dir itself is rm -rf'd:
# state_dir was already computed in Step 5 via state_dir_path()
if [ -n "$state_dir" ]; then
# Pass --keep-forensics if the user requested it (e.g. they want to inspect
# heartbeat.json or revision-log/<session>.jsonl after stop).
KEEP_FLAG=""
for arg in "$@"; do
[ "$arg" = "--keep-forensics" ] && KEEP_FLAG="--keep-forensics"
done
cleanup_state_dir "$state_dir" $KEEP_FLAG
fi
cleanup_state_dir is exported by state-lib.sh. It refuses to operate on paths outside $HOME (safety guard). Tarball failure is non-fatal — it warns and proceeds with the rm.
Step 8: Update frontmatter
Edit the YAML frontmatter exit_condition field to include DONE so the next firing detects it immediately without scanning the body.
Step 9: Suggest final commit
Print a suggested commit:
git add "$CONTRACT_PATH"
git commit -m "$(cat <<'EOF'
loop(stop): <loop-name> complete — <reason>
Final iteration: <N>
Last action: <summary from Current State>
EOF
)"
Anti-patterns
- Do NOT
kill -9any running process from this skill — that's out of scope. This skill only signals the loop; separate skills (likeru:stoporpueue kill) handle process termination. - Do NOT rewrite the Revision Log — append-only. Add a DONE section instead.
- Do NOT send
PushNotificationif the user is actively present (watch for a recent user turn in the last 60 seconds); that would be annoying. When in doubt, skip the push.
Troubleshooting
| Symptom | Fix |
| ---------------------------------- | ---------------------------------------------------------------------------------------------- |
| PushNotification tool not loaded | ToolSearch with query select:PushNotification before step 4 |
| DONE section already exists | Loop was already stopped; print confirmation and exit |
| Loop keeps firing after DONE | The next /loop reading the contract should short-circuit — verify pointer trigger is correct |
Post-Execution Reflection
- Locate yourself. — Confirm this SKILL.md is the canonical file before any edit.
- What failed? — Fix the instruction that caused it.
- What drifted? — Update DONE-section template if needed.
- Log it. — Evolution-log entry.