cancel
Cleanly exit any active persistent mode (ralph, ultrawork, autopilot, ralplan, team, swarm) and clean up all state files
Input: $ARGUMENTS — pass --force to delete state files even when no active mode is detected (recovery mode for corrupt/orphaned state).
Cancel any active persistent mode
This command is the clean exit point for any dog_stack persistence loop: /dog:ralph, /dog:autopilot, /dog:ultrawork, /dog:ralplan, /dog:team, or swarm mode. The stop hook in hooks/persistent-mode.cjs reads state files under .dog/state/ and re-injects continuation prompts whenever a mode is marked active: true. This command removes those state files so the loop terminates naturally on the next Stop hook evaluation.
Mode state files managed
Each persistent mode writes to its own JSON file under .dog/state/sessions/<session-id>/ (session-scoped) with fallback to .dog/state/ (legacy unscoped):
| Mode | State file | Entry point |
|---|---|---|
| Ralph | ralph-state.json | /dog:ralph |
| Ultrawork | ultrawork-state.json | /dog:ultrawork |
| Autopilot | autopilot-state.json | /dog:autopilot |
| Ralplan | ralplan-state.json | PRP plan execution |
| Team | team-state.json | /dog:team |
| OMC Teams | omc-teams-state.json | (OMC compat) |
| Swarm | swarm-summary.json + swarm-active.marker | team swarm mode |
Execute these steps
Run the following single bash block. It uses a bash array for path safety (paths may contain spaces), falls back to grep/sed when jq is unavailable, and reports a clean summary at the end.
#!/usr/bin/env bash
set -u
STATE_DIR="$(pwd)/.dog/state"
FORCE=0
case "$ARGUMENTS" in *--force*) FORCE=1 ;; esac
declare -a FOUND=()
# Detect state files across legacy unscoped path + all session-scoped paths
STATE_FILES=(
"ralph-state.json"
"ultrawork-state.json"
"autopilot-state.json"
"ralplan-state.json"
"team-state.json"
"omc-teams-state.json"
"swarm-summary.json"
)
for name in "${STATE_FILES[@]}"; do
# Legacy unscoped path
[ -f "$STATE_DIR/$name" ] && FOUND+=("$STATE_DIR/$name")
# All session-scoped paths
if [ -d "$STATE_DIR/sessions" ]; then
while IFS= read -r -d '' path; do
FOUND+=("$path")
done < <(find "$STATE_DIR/sessions" -mindepth 2 -maxdepth 2 -type f -name "$name" -print0 2>/dev/null)
fi
done
# Swarm active marker (separate file, not JSON)
[ -f "$STATE_DIR/swarm-active.marker" ] && FOUND+=("$STATE_DIR/swarm-active.marker")
if [ -d "$STATE_DIR/sessions" ]; then
while IFS= read -r -d '' marker; do
FOUND+=("$marker")
done < <(find "$STATE_DIR/sessions" -mindepth 2 -maxdepth 2 -type f -name "swarm-active.marker" -print0 2>/dev/null)
fi
# Handle empty case
if [ "${#FOUND[@]}" -eq 0 ]; then
if [ "$FORCE" -eq 0 ]; then
echo "No active persistent mode detected. Nothing to cancel."
exit 0
fi
echo "No active state files. Proceeding with --force cleanup of orphaned session dirs..."
fi
# Report per-file info before deletion
if [ "${#FOUND[@]}" -gt 0 ]; then
echo "Detected ${#FOUND[@]} active state file(s):"
for f in "${FOUND[@]}"; do
case "$f" in
*.marker) echo " - swarm (active marker: $f)"; continue ;;
esac
base="$(basename "$f")"
mode="${base%-state.json}"
iter="n/a"; maxiter="n/a"; started="n/a"; active="?"
if command -v jq >/dev/null 2>&1; then
iter=$(jq -r '.iteration // "n/a"' "$f" 2>/dev/null)
maxiter=$(jq -r '.max_iterations // "n/a"' "$f" 2>/dev/null)
started=$(jq -r '.started_at // "n/a"' "$f" 2>/dev/null)
active=$(jq -r '.active // "?"' "$f" 2>/dev/null)
else
# Fallback: grep the JSON manually for simple scalar fields
iter=$(grep -oE '"iteration"[[:space:]]*:[[:space:]]*[0-9]+' "$f" | head -1 | grep -oE '[0-9]+$' || echo "n/a")
maxiter=$(grep -oE '"max_iterations"[[:space:]]*:[[:space:]]*[0-9]+' "$f" | head -1 | grep -oE '[0-9]+$' || echo "n/a")
started=$(grep -oE '"started_at"[[:space:]]*:[[:space:]]*"[^"]*"' "$f" | head -1 | sed -E 's/.*:[[:space:]]*"([^"]*)".*/\1/' || echo "n/a")
active=$(grep -oE '"active"[[:space:]]*:[[:space:]]*(true|false)' "$f" | head -1 | grep -oE '(true|false)$' || echo "?")
fi
[ -z "$iter" ] && iter="n/a"
[ -z "$maxiter" ] && maxiter="n/a"
[ -z "$started" ] && started="n/a"
[ -z "$active" ] && active="?"
echo " - $mode — active=$active, iteration=$iter/$maxiter, started=$started"
done
fi
# Delete state files
DELETED=0
for f in "${FOUND[@]}"; do
if rm -f "$f"; then
DELETED=$((DELETED + 1))
fi
done
# Prune empty session dirs (always, not just on --force — keeps state clean)
PRUNED=0
if [ -d "$STATE_DIR/sessions" ]; then
while IFS= read -r -d '' dir; do
rmdir "$dir" 2>/dev/null && PRUNED=$((PRUNED + 1))
done < <(find "$STATE_DIR/sessions" -mindepth 1 -maxdepth 1 -type d -empty -print0 2>/dev/null)
fi
echo ""
echo "Cancelled $DELETED state file(s). Pruned $PRUNED empty session dir(s)."
echo "Loop will terminate on next Stop hook evaluation."
After running the block, summarize in plain text: how many files were cleaned, which modes were active, and confirm the loop will exit. If $ARGUMENTS contained --force and no files were found, explicitly say so.
Design notes
- Path-safe: uses bash arrays and
find -print0to handle paths with spaces (e.g.C:\Users\roger\My Projects\...). - jq-optional: falls back to grep/sed when
jqisn't installed (Windows Git Bash commonly lacks jq). - Scope-limited: only removes files under
.dog/state/. Never touches git, source code,.dog/PRPs/, or anything else. - Idempotent: running twice is a no-op.
- Empty-dir pruning: always cleans empty session directories to keep
.dog/state/sessions/tidy, even without--force. - Modeled after:
anthropics/claude-plugins-official/plugins/ralph-loop/commands/cancel-ralph.md(single-file cancel pattern) + adapted for dog_stack's session-scoped multi-mode state layout.
When to use
- After a persistence loop completes and you want to stop the Stop hook from re-injecting prompts
- When a loop is stuck awaiting confirmation and you want to exit manually
- When state files are corrupt or point to a dead session (
--force) - Before starting a new persistence loop to avoid collisions with stale state