From 52a0ba0da0d70cffa79c04bc56f3990c3b5fd568 Mon Sep 17 00:00:00 2001 From: Clawd Date: Sat, 21 Feb 2026 18:47:13 +0100 Subject: [PATCH] docs: add Stop hook implementation plan Co-Authored-By: Claude Sonnet 4.6 --- docs/plans/2026-02-21-stop-hook.md | 153 +++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 docs/plans/2026-02-21-stop-hook.md diff --git a/docs/plans/2026-02-21-stop-hook.md b/docs/plans/2026-02-21-stop-hook.md new file mode 100644 index 0000000..6c6b908 --- /dev/null +++ b/docs/plans/2026-02-21-stop-hook.md @@ -0,0 +1,153 @@ +# Stop Hook Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Add a Stop hook to `.claude/settings.json` that blocks Claude from stopping if there are uncommitted tracked-file changes or unresolved conversational TODOs. + +**Architecture:** Two parallel hooks registered under the `Stop` event in `.claude/settings.json`. Hook 1 is a bash command that checks `git diff --name-only HEAD`; Hook 2 is a prompt hook that reads the conversation transcript for unresolved work. + +**Tech Stack:** Bash, Claude Code hooks API (settings.json), git + +--- + +### Task 1: Create the git clean check script + +**Files:** +- Create: `.claude/hooks/git-clean-check.sh` + +**Step 1: Create the hooks directory** + +```bash +mkdir -p /workspace/gravl/.claude/hooks +``` + +**Step 2: Write the script** + +Create `.claude/hooks/git-clean-check.sh`: + +```bash +#!/bin/bash +set -euo pipefail + +dirty=$(git -C "${CLAUDE_PROJECT_DIR:-.}" diff --name-only HEAD 2>/dev/null) + +if [ -n "$dirty" ]; then + # Format list for the reason message + file_list=$(echo "$dirty" | tr '\n' ', ' | sed 's/, $//') + printf '{"decision": "block", "reason": "Uncommitted changes in tracked files: %s"}' "$file_list" >&2 + exit 2 +fi + +echo '{"decision": "approve"}' +``` + +**Step 3: Make it executable** + +```bash +chmod +x /workspace/gravl/.claude/hooks/git-clean-check.sh +``` + +**Step 4: Manually test the script** + +With the current dirty working tree it should block: + +```bash +cd /workspace/gravl && bash .claude/hooks/git-clean-check.sh +``` + +Expected: exits 2, stderr contains `{"decision": "block", ...}` listing modified files. + +**Step 5: Test the approve path** + +```bash +cd /workspace/gravl && git stash && bash .claude/hooks/git-clean-check.sh; git stash pop +``` + +Expected: exits 0, stdout is `{"decision": "approve"}`. + +**Step 6: Commit** + +```bash +git -C /workspace/gravl add .claude/hooks/git-clean-check.sh +git -C /workspace/gravl commit -m "feat(hooks): add git clean check script for Stop hook" +``` + +--- + +### Task 2: Register hooks in settings.json + +**Files:** +- Create: `.claude/settings.json` + +Note: `.claude/settings.local.json` already exists with `permissions`. The new `settings.json` is for hooks (tracked by git; `settings.local.json` is not). + +**Step 1: Write settings.json** + +Create `.claude/settings.json`: + +```json +{ + "hooks": { + "Stop": [ + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "bash $CLAUDE_PROJECT_DIR/.claude/hooks/git-clean-check.sh", + "timeout": 10 + }, + { + "type": "prompt", + "prompt": "Review the conversation transcript. Look for any tasks, TODOs, follow-up items, or promises Claude made (e.g. \"I'll fix X\", \"TODO:\", \"we should also…\", \"I'll address that later\") that were mentioned but not completed in this session. If any are unresolved, return {\"decision\": \"block\", \"reason\": \"Unresolved items: \"}. If everything mentioned was completed, return {\"decision\": \"approve\"}.", + "timeout": 30 + } + ] + } + ] + } +} +``` + +**Step 2: Validate JSON** + +```bash +jq . /workspace/gravl/.claude/settings.json +``` + +Expected: JSON reprinted with no errors. + +**Step 3: Commit** + +```bash +git -C /workspace/gravl add .claude/settings.json +git -C /workspace/gravl commit -m "feat(hooks): register Stop hook in settings.json" +``` + +--- + +### Task 3: Verify hooks load in Claude Code + +**Step 1: Restart Claude Code** + +Hooks load at session start. Exit and reopen `claude` in `/workspace/gravl`. + +**Step 2: Check hooks loaded** + +Run `/hooks` inside Claude Code. + +Expected: Stop hook appears with two entries (command + prompt). + +**Step 3: Trigger the hook manually** + +Ask Claude to stop (or say "ok thanks, bye"). With current dirty state, the git hook should block and report uncommitted files. + +**Step 4: Commit everything clean and verify approve path** + +```bash +git -C /workspace/gravl add -A && git -C /workspace/gravl commit -m "chore: clean working tree to test Stop hook approve path" +``` + +Then attempt to stop Claude — both hooks should approve. + +---