Skip to content

Commit 6a9a73d

Browse files
Merge pull request #632 from puppetlabs/CAT-2609
(CAT-2609) Add safety hooks for Claude Code
2 parents 3913121 + 5749789 commit 6a9a73d

7 files changed

Lines changed: 92 additions & 0 deletions

File tree

.claude/hooks/_parse_input.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
# Shared helper: reads hook JSON from stdin and sets $cmd to the Bash tool command.
3+
# Sources into each hook with: . "$(dirname "$0")/_parse_input.sh"
4+
# Exits 0 (allow) if no JSON parser is available.
5+
6+
if command -v jq >/dev/null 2>&1; then
7+
# shellcheck disable=SC2034 # cmd is used by the sourcing hook script
8+
cmd=$(jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
9+
elif command -v python3 >/dev/null 2>&1; then
10+
# shellcheck disable=SC2034 # cmd is used by the sourcing hook script
11+
cmd=$(python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null || echo "")
12+
else
13+
exit 0
14+
fi

.claude/hooks/no-main-commits.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env bash
2+
# Project rule: never work directly on the main or master branch.
3+
# Blocks: git commit when current branch is main or master.
4+
5+
# shellcheck source=/dev/null
6+
. "$(dirname "$0")/_parse_input.sh"
7+
8+
if echo "$cmd" | grep -qE '(^|[;&|`(])[[:space:]]*(([[:alpha:]_][[:alnum:]_]*=[^[:space:]]*[[:space:]]+)*)([^[:space:]]*/)?git[[:space:]]+commit([[:space:]]|$|[;&|])'; then
9+
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
10+
if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
11+
echo "{\"continue\":false,\"stopReason\":\"Project rule: never work directly on the $branch branch.\"}"
12+
fi
13+
fi

.claude/hooks/no-pr-merge.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env bash
2+
# Project rule: never merge a pull request.
3+
# Blocks: gh pr merge
4+
5+
# shellcheck source=/dev/null
6+
. "$(dirname "$0")/_parse_input.sh"
7+
8+
if echo "$cmd" | grep -qE '(^|[;&|])[[:space:]]*gh pr merge'; then
9+
echo '{"continue":false,"stopReason":"Project rule: never merge a pull request."}'
10+
fi

.claude/hooks/no-push.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env bash
2+
# Project rule: never push a branch without explicit instruction.
3+
# Blocks: git push
4+
5+
# shellcheck source=/dev/null
6+
. "$(dirname "$0")/_parse_input.sh"
7+
8+
if echo "$cmd" | grep -qE '(^|[;&|`(])[[:space:]]*(([[:alpha:]_][[:alnum:]_]*=[^[:space:]]*[[:space:]]+)*)([^[:space:]]*/)?git[[:space:]]+push([[:space:]]|$|[;&|])'; then
9+
echo '{"continue":false,"stopReason":"Project rule: never push a branch without explicit instruction."}'
10+
fi

.claude/hooks/no-rm.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
# Project rule: never delete a file without explicit permission.
3+
# Blocks: rm, sudo rm, xargs rm, /bin/rm, /usr/bin/rm (and similar absolute paths).
4+
# Does not cover: find -exec rm (rm as a subprocess argument, not a shell token).
5+
6+
# shellcheck source=/dev/null
7+
. "$(dirname "$0")/_parse_input.sh"
8+
9+
if echo "$cmd" | grep -qE '(^|[;&|])[[:space:]]*(sudo[[:space:]]+|xargs[[:space:]]+)?([^[:space:]]*/)?rm([[:space:]]|$)'; then
10+
echo '{"continue":false,"stopReason":"Project rule: never delete a file without explicit permission."}'
11+
fi

.claude/settings.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"hooks": {
3+
"PreToolUse": [
4+
{
5+
"matcher": "Bash",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": ".claude/hooks/no-pr-merge.sh",
10+
"timeout": 5,
11+
"statusMessage": "Checking project rules..."
12+
},
13+
{
14+
"type": "command",
15+
"command": ".claude/hooks/no-main-commits.sh",
16+
"timeout": 5
17+
},
18+
{
19+
"type": "command",
20+
"command": ".claude/hooks/no-push.sh",
21+
"timeout": 5
22+
},
23+
{
24+
"type": "command",
25+
"command": ".claude/hooks/no-rm.sh",
26+
"timeout": 5
27+
}
28+
]
29+
}
30+
]
31+
}
32+
}

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ If you change template behavior, update:
9696

9797
## Project Rules
9898

99+
- At the start of a coding session, review the repository structure and any relevant README or documentation files to understand the area you are working in.
100+
- Always read the files relevant to the task before suggesting or making a change.
99101
- Never merge a pull request.
100102
- Never work directly on the `main` or `master` branch.
101103
- Never push a branch without explicit instruction.

0 commit comments

Comments
 (0)