cc-safe-setup 28.4.3 → 28.4.5
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.
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# hook-permission-fixer.sh — Auto-fix missing execute permissions on hooks
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Claude Code's plugin manager and some extraction tools strip
|
|
7
|
+
# execute permissions from shell scripts. This hook runs at session
|
|
8
|
+
# start and ensures all .sh files in the hooks directory are executable.
|
|
9
|
+
#
|
|
10
|
+
# Without this, hooks fail with "Permission denied" errors.
|
|
11
|
+
# See: github.com/anthropics/claude-code/issues/38901
|
|
12
|
+
#
|
|
13
|
+
# TRIGGER: SessionStart MATCHER: ""
|
|
14
|
+
# ================================================================
|
|
15
|
+
|
|
16
|
+
HOOKS_DIR="$HOME/.claude/hooks"
|
|
17
|
+
PLUGINS_DIR="$HOME/.claude/plugins"
|
|
18
|
+
FIXED=0
|
|
19
|
+
|
|
20
|
+
# Fix hooks directory
|
|
21
|
+
if [ -d "$HOOKS_DIR" ]; then
|
|
22
|
+
for f in "$HOOKS_DIR"/*.sh; do
|
|
23
|
+
[ -f "$f" ] || continue
|
|
24
|
+
if [ ! -x "$f" ]; then
|
|
25
|
+
chmod +x "$f"
|
|
26
|
+
FIXED=$((FIXED + 1))
|
|
27
|
+
fi
|
|
28
|
+
done
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Fix plugin hooks
|
|
32
|
+
if [ -d "$PLUGINS_DIR" ]; then
|
|
33
|
+
while IFS= read -r -d '' f; do
|
|
34
|
+
if [ ! -x "$f" ]; then
|
|
35
|
+
chmod +x "$f"
|
|
36
|
+
FIXED=$((FIXED + 1))
|
|
37
|
+
fi
|
|
38
|
+
done < <(find "$PLUGINS_DIR" -name "*.sh" -print0 2>/dev/null)
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
if [ "$FIXED" -gt 0 ]; then
|
|
42
|
+
echo "Fixed execute permissions on $FIXED hook script(s)" >&2
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
exit 0
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# response-budget-guard.sh — Track and limit tool calls per response
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Prevents runaway tool call loops where Claude calls hundreds of
|
|
7
|
+
# tools in a single response. Common in autonomous mode when the
|
|
8
|
+
# agent enters a retry loop or tries to brute-force a solution.
|
|
9
|
+
#
|
|
10
|
+
# Tracks tool calls per response cycle and warns after threshold.
|
|
11
|
+
#
|
|
12
|
+
# TRIGGER: PreToolUse MATCHER: ""
|
|
13
|
+
#
|
|
14
|
+
# CONFIG:
|
|
15
|
+
# CC_RESPONSE_TOOL_LIMIT=50 (warn after this many tool calls)
|
|
16
|
+
# ================================================================
|
|
17
|
+
|
|
18
|
+
LIMIT="${CC_RESPONSE_TOOL_LIMIT:-50}"
|
|
19
|
+
STATE="/tmp/cc-response-budget-$(echo "$PWD" | md5sum | cut -c1-8)"
|
|
20
|
+
|
|
21
|
+
# Read current count
|
|
22
|
+
COUNT=0
|
|
23
|
+
if [ -f "$STATE" ]; then
|
|
24
|
+
COUNT=$(cat "$STATE")
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
COUNT=$((COUNT + 1))
|
|
28
|
+
echo "$COUNT" > "$STATE"
|
|
29
|
+
|
|
30
|
+
if [ "$COUNT" -eq "$LIMIT" ]; then
|
|
31
|
+
echo "WARNING: $COUNT tool calls in this response cycle." >&2
|
|
32
|
+
echo "Consider whether you're in a retry loop." >&2
|
|
33
|
+
echo "Reset: rm $STATE" >&2
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Hard block at 2x limit to prevent truly runaway sessions
|
|
37
|
+
if [ "$COUNT" -gt $((LIMIT * 2)) ]; then
|
|
38
|
+
echo "BLOCKED: $COUNT tool calls exceeds safety limit ($((LIMIT * 2)))." >&2
|
|
39
|
+
echo "You appear to be in a loop. Stop and reassess." >&2
|
|
40
|
+
exit 2
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
exit 0
|
package/index.mjs
CHANGED
|
@@ -405,6 +405,8 @@ function examples() {
|
|
|
405
405
|
'pip-venv-guard.sh': 'Warn on pip install outside venv',
|
|
406
406
|
'no-git-amend-push.sh': 'Warn on amending pushed commits',
|
|
407
407
|
'typosquat-guard.sh': 'Detect npm/pip typosquatting attacks',
|
|
408
|
+
'hook-permission-fixer.sh': 'Auto-fix missing execute permissions on hooks (SessionStart)',
|
|
409
|
+
'response-budget-guard.sh': 'Track and limit tool calls per response (anti-loop)',
|
|
408
410
|
},
|
|
409
411
|
'Auto-Approve': {
|
|
410
412
|
'auto-approve-build.sh': 'Auto-approve npm/yarn/cargo/go build, test, lint',
|
|
@@ -1573,7 +1575,7 @@ async function guard(description) {
|
|
|
1573
1575
|
if (!settings.hooks) settings.hooks = {};
|
|
1574
1576
|
if (!settings.hooks[trigger]) settings.hooks[trigger] = [];
|
|
1575
1577
|
|
|
1576
|
-
const cmd = `bash ${hookPath}`;
|
|
1578
|
+
const cmd = `bash ${toBashPath(hookPath)}`;
|
|
1577
1579
|
const alreadyExists = JSON.stringify(settings.hooks).includes(hookName);
|
|
1578
1580
|
if (!alreadyExists) {
|
|
1579
1581
|
const existing = settings.hooks[trigger].find(e => e.matcher === matcher);
|
|
@@ -2435,7 +2437,7 @@ async function shield() {
|
|
|
2435
2437
|
|
|
2436
2438
|
for (const f of hookFiles) {
|
|
2437
2439
|
const content = readFileSync(join(HOOKS_DIR, f), 'utf-8');
|
|
2438
|
-
const cmd = `bash ${join(HOOKS_DIR, f)}`;
|
|
2440
|
+
const cmd = `bash ${toBashPath(join(HOOKS_DIR, f))}`;
|
|
2439
2441
|
|
|
2440
2442
|
// Check if already in settings
|
|
2441
2443
|
const alreadyConfigured = JSON.stringify(settings.hooks).includes(f);
|
|
@@ -4557,7 +4559,7 @@ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
|
4557
4559
|
if (!settings.hooks) settings.hooks = {};
|
|
4558
4560
|
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
4559
4561
|
|
|
4560
|
-
const hookCmd = `bash ${hookPath}`;
|
|
4562
|
+
const hookCmd = `bash ${toBashPath(hookPath)}`;
|
|
4561
4563
|
|
|
4562
4564
|
// Check all matchers for existing compiled-rules entry
|
|
4563
4565
|
let found = false;
|
|
@@ -4679,7 +4681,7 @@ exit 0
|
|
|
4679
4681
|
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
4680
4682
|
|
|
4681
4683
|
// Register under specific matchers based on rule types (NEVER use "" matcher)
|
|
4682
|
-
const hookCmd = `bash ${hookPath}`;
|
|
4684
|
+
const hookCmd = `bash ${toBashPath(hookPath)}`;
|
|
4683
4685
|
const hasBlocks = rules.some(r => r.type === 'block');
|
|
4684
4686
|
const hasApproves = rules.some(r => r.type === 'approve');
|
|
4685
4687
|
const hasProtects = rules.some(r => r.type === 'protect');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "28.4.
|
|
4
|
-
"description": "One command to make Claude Code safe. 336 hooks (8 built-in +
|
|
3
|
+
"version": "28.4.5",
|
|
4
|
+
"description": "One command to make Claude Code safe. 336 hooks (8 built-in + 330 examples). 49 CLI commands. 987 tests. 5 languages.",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cc-safe-setup": "index.mjs"
|