cc-safe-setup 28.5.0 → 28.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COOKBOOK.md +10 -0
- package/README.md +1 -1
- package/TROUBLESHOOTING.md +44 -0
- package/examples/allow-claude-settings.sh +33 -0
- package/examples/allow-git-hooks-dir.sh +29 -0
- package/examples/allow-protected-dirs.sh +32 -0
- package/index.mjs +6 -3
- package/package.json +2 -2
package/COOKBOOK.md
CHANGED
|
@@ -135,6 +135,16 @@ All browser-based, nothing leaves your machine:
|
|
|
135
135
|
- [Playground](https://yurukusa.github.io/cc-safe-setup/playground.html) — Write and test hooks
|
|
136
136
|
- [Hook Builder](https://yurukusa.github.io/cc-safe-setup/builder.html) — Generate hooks from English
|
|
137
137
|
|
|
138
|
+
## 27. Bypass Protected Directory Prompts (PermissionRequest)
|
|
139
|
+
|
|
140
|
+
PreToolUse hooks can't bypass built-in protected-directory checks — they run *before* those checks. Use PermissionRequest instead:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npx cc-safe-setup --install-example allow-git-hooks-dir
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Or manually: create a PermissionRequest hook that outputs `permissionDecision: "allow"`. See [Troubleshooting](TROUBLESHOOTING.md#pretooluse-allow-doesnt-bypass-protected-directory-prompts) for details.
|
|
147
|
+
|
|
138
148
|
## Further Reading
|
|
139
149
|
|
|
140
150
|
- [Getting Started](https://yurukusa.github.io/cc-safe-setup/getting-started.html)
|
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ Each hook exists because a real incident happened without it.
|
|
|
87
87
|
| `--scan [--apply]` | Tech stack detection |
|
|
88
88
|
| `--export / --import` | Team config sharing |
|
|
89
89
|
| `--verify` | Test each hook |
|
|
90
|
-
| `--install-example <name>` | Install from
|
|
90
|
+
| `--install-example <name>` | Install from 333 examples |
|
|
91
91
|
| `--examples [filter]` | Browse examples by keyword |
|
|
92
92
|
| `--full` | All-in-one setup |
|
|
93
93
|
| `--status` | Check installed hooks |
|
package/TROUBLESHOOTING.md
CHANGED
|
@@ -132,6 +132,34 @@ jq -n '...'
|
|
|
132
132
|
|
|
133
133
|
Auto-approve JSON must go to stdout.
|
|
134
134
|
|
|
135
|
+
## "PreToolUse allow doesn't bypass protected directory prompts"
|
|
136
|
+
|
|
137
|
+
This is expected behavior, not a bug.
|
|
138
|
+
|
|
139
|
+
**Execution order:**
|
|
140
|
+
1. PreToolUse hooks run
|
|
141
|
+
2. Built-in protected-directory checks run (`.claude/`, `.git/`, etc.)
|
|
142
|
+
3. PermissionRequest hooks run
|
|
143
|
+
|
|
144
|
+
PreToolUse's `permissionDecision: "allow"` gets overridden by the built-in checks in step 2. To bypass protected directory prompts, use **PermissionRequest** hooks instead:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
#!/bin/bash
|
|
148
|
+
# Save as: ~/.claude/hooks/allow-protected-dir.sh
|
|
149
|
+
# Trigger: PermissionRequest (not PreToolUse)
|
|
150
|
+
INPUT=$(cat)
|
|
151
|
+
PATH_TARGET=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.command // empty')
|
|
152
|
+
|
|
153
|
+
# Allow writes to a specific protected directory
|
|
154
|
+
if echo "$PATH_TARGET" | grep -q '/my-project/.git/hooks'; then
|
|
155
|
+
jq -n '{hookSpecificOutput: {hookEventName: "PermissionRequest", permissionDecision: "allow", permissionDecisionReason: "Allowed: git hooks directory"}}'
|
|
156
|
+
exit 0
|
|
157
|
+
fi
|
|
158
|
+
exit 0
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Rule of thumb:** PreToolUse = block dangerous actions. PermissionRequest = allow trusted actions that trigger built-in prompts.
|
|
162
|
+
|
|
135
163
|
## "Permission prompts still appear for compound commands"
|
|
136
164
|
|
|
137
165
|
This is a known Claude Code limitation, not a hook issue. `Bash(git:*)` doesn't match `cd /path && git log`.
|
|
@@ -204,6 +232,22 @@ echo "[$(date -Iseconds)] BLOCKED: reason | cmd: $COMMAND" >> "$LOG"
|
|
|
204
232
|
|
|
205
233
|
Then view with: `npx cc-safe-setup --watch` or `npx cc-safe-setup --stats`
|
|
206
234
|
|
|
235
|
+
## "claude -p returns empty output when Stop hook is configured"
|
|
236
|
+
|
|
237
|
+
This is a known Claude Code v2.1.83 bug ([#38651](https://github.com/anthropics/claude-code/issues/38651)), not a cc-safe-setup issue. Any Stop hook — even `true` — causes `-p` (print mode) to return empty stdout.
|
|
238
|
+
|
|
239
|
+
**Workaround:** Temporarily remove Stop hooks when using `-p` mode:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# Quick toggle: comment out Stop hooks before -p commands
|
|
243
|
+
npx cc-safe-setup --status # See which hooks are active
|
|
244
|
+
# Manually comment out Stop hooks in ~/.claude/settings.json
|
|
245
|
+
# Run your -p command
|
|
246
|
+
# Uncomment Stop hooks after
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
This should be fixed in a future Claude Code release.
|
|
250
|
+
|
|
207
251
|
## Still Stuck?
|
|
208
252
|
|
|
209
253
|
1. Wrap the hook with debug wrapper: `npx cc-safe-setup --install-example hook-debug-wrapper`
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# allow-claude-settings.sh — PermissionRequest hook
|
|
3
|
+
# Trigger: PermissionRequest
|
|
4
|
+
# Matcher: Edit|Write
|
|
5
|
+
#
|
|
6
|
+
# Auto-approves writes to .claude/ configuration files.
|
|
7
|
+
# Use this in isolated environments (containers, VMs) where
|
|
8
|
+
# bypassPermissions is enabled but .claude/ writes still prompt.
|
|
9
|
+
#
|
|
10
|
+
# See: https://github.com/anthropics/claude-code/issues/36044
|
|
11
|
+
# See: https://github.com/anthropics/claude-code/issues/37765
|
|
12
|
+
#
|
|
13
|
+
# WARNING: Only use in environments where you trust Claude's edits
|
|
14
|
+
# to your configuration. In shared or production environments,
|
|
15
|
+
# keep the default prompts.
|
|
16
|
+
|
|
17
|
+
INPUT=$(cat)
|
|
18
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
19
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
20
|
+
|
|
21
|
+
# Allow .claude/ writes (settings, rules, agents, skills, hooks)
|
|
22
|
+
if echo "$FILE_PATH" | grep -qE '\.claude/'; then
|
|
23
|
+
jq -n '{
|
|
24
|
+
hookSpecificOutput: {
|
|
25
|
+
hookEventName: "PermissionRequest",
|
|
26
|
+
permissionDecision: "allow",
|
|
27
|
+
permissionDecisionReason: "Allowed: .claude/ directory (isolated environment)"
|
|
28
|
+
}
|
|
29
|
+
}'
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
exit 0
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# allow-git-hooks-dir.sh — PermissionRequest hook
|
|
3
|
+
# Trigger: PermissionRequest
|
|
4
|
+
# Matcher: Edit|Write
|
|
5
|
+
#
|
|
6
|
+
# Bypasses the built-in protected-directory prompt for .git/hooks/.
|
|
7
|
+
# PreToolUse hooks can't do this — they run before built-in checks.
|
|
8
|
+
# PermissionRequest runs after, so it can override the prompt.
|
|
9
|
+
#
|
|
10
|
+
# WARNING: Only allow specific subdirectories you trust.
|
|
11
|
+
# Never blanket-allow all of .git/ — that exposes HEAD, config, etc.
|
|
12
|
+
|
|
13
|
+
INPUT=$(cat)
|
|
14
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
15
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
16
|
+
|
|
17
|
+
# Only allow .git/hooks/ writes (e.g., pre-commit, pre-push)
|
|
18
|
+
if echo "$FILE_PATH" | grep -qE '\.git/hooks/[^/]+$'; then
|
|
19
|
+
jq -n '{
|
|
20
|
+
hookSpecificOutput: {
|
|
21
|
+
hookEventName: "PermissionRequest",
|
|
22
|
+
permissionDecision: "allow",
|
|
23
|
+
permissionDecisionReason: "Allowed: git hooks directory"
|
|
24
|
+
}
|
|
25
|
+
}'
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
exit 0
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# allow-protected-dirs.sh — PermissionRequest hook
|
|
3
|
+
# Trigger: PermissionRequest
|
|
4
|
+
# Matcher: Edit|Write
|
|
5
|
+
#
|
|
6
|
+
# Auto-approves writes to ALL protected directories (.claude/, .git/,
|
|
7
|
+
# .vscode/, .idea/). Equivalent to full bypassPermissions for file edits.
|
|
8
|
+
#
|
|
9
|
+
# USE CASE: Docker containers, CI/CD, disposable VMs where you want
|
|
10
|
+
# zero prompts and understand the risks.
|
|
11
|
+
#
|
|
12
|
+
# WARNING: This is the most permissive PermissionRequest hook possible.
|
|
13
|
+
# It bypasses ALL built-in file protection. Do NOT use in shared or
|
|
14
|
+
# production environments. Prefer allow-git-hooks-dir.sh or
|
|
15
|
+
# allow-claude-settings.sh for targeted bypass.
|
|
16
|
+
|
|
17
|
+
INPUT=$(cat)
|
|
18
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
19
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
20
|
+
|
|
21
|
+
if echo "$FILE_PATH" | grep -qE '\.(claude|git|vscode|idea)/'; then
|
|
22
|
+
jq -n '{
|
|
23
|
+
hookSpecificOutput: {
|
|
24
|
+
hookEventName: "PermissionRequest",
|
|
25
|
+
permissionDecision: "allow",
|
|
26
|
+
permissionDecisionReason: "Allowed: protected directory (full bypass)"
|
|
27
|
+
}
|
|
28
|
+
}'
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
exit 0
|
package/index.mjs
CHANGED
|
@@ -601,9 +601,12 @@ async function installExample(name) {
|
|
|
601
601
|
let matcher = 'Bash';
|
|
602
602
|
|
|
603
603
|
// Detect trigger from header comments
|
|
604
|
-
if (content.includes('PostToolUse')) trigger = 'PostToolUse';
|
|
605
|
-
if (content.includes('Notification')) trigger = 'Notification';
|
|
606
|
-
if (content.includes('Stop')) trigger = 'Stop';
|
|
604
|
+
if (content.includes('TRIGGER: PostToolUse') || content.includes('PostToolUse')) trigger = 'PostToolUse';
|
|
605
|
+
if (content.includes('TRIGGER: Notification') || content.includes('Notification')) trigger = 'Notification';
|
|
606
|
+
if (content.includes('TRIGGER: Stop') || content.includes('Stop')) trigger = 'Stop';
|
|
607
|
+
if (content.includes('TRIGGER: SessionStart') || content.includes('SessionStart')) trigger = 'SessionStart';
|
|
608
|
+
if (content.includes('TRIGGER: PreCompact') || content.includes('PreCompact')) trigger = 'PreCompact';
|
|
609
|
+
if (content.includes('TRIGGER: SessionEnd') || content.includes('SessionEnd')) trigger = 'SessionEnd';
|
|
607
610
|
|
|
608
611
|
// Detect matcher from header
|
|
609
612
|
const matcherMatch = content.match(/"matcher":\s*"([^"]*)"/);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "28.
|
|
4
|
-
"description": "One command to make Claude Code safe.
|
|
3
|
+
"version": "28.7.0",
|
|
4
|
+
"description": "One command to make Claude Code safe. 339 hooks (8 built-in + 333 examples). 49 CLI commands. 1009 tests. 5 languages.",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cc-safe-setup": "index.mjs"
|