Skip to content

Commit 0f910a9

Browse files
committed
chore: harden global-deny hook (runcycles/.github#63)
Propagation from cycles-spring-boot-starter#64.
1 parent 0c18750 commit 0f910a9

1 file changed

Lines changed: 31 additions & 45 deletions

File tree

.claude/session-start-global-deny.sh

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,29 @@
11
#!/bin/bash
2-
# CANONICAL SOURCE — managed by runcycles/.github/shared-config/
3-
# Do not edit this file in individual repos. Changes should be made here
4-
# and synced to all repos via scripts/sync-claude-config.sh.
2+
# Session start hook: ensure global Claude Code deny rules and git proxy config
53
#
6-
# Session start hook: ensure global Claude Code deny rules and git proxy config.
7-
#
8-
# This script runs at session start and does TWO things:
9-
#
10-
# 1. (Per-user, idempotent) Writes MCP deny rules to ~/.claude/settings.json
11-
# so mcp__github__ file-mutation tools are blocked globally — even in
12-
# cross-repo sessions where deny rules in a single repo wouldn't apply.
13-
#
14-
# 2. (MULTI-REPO MUTATION) If a local git proxy is detected in any sibling
15-
# repo under /home/user/*, rewrites the `origin` remote URL of EVERY
16-
# sibling github.com repo under /home/user/ to route through the proxy.
17-
# This is intentional for Claude Code remote-environment workflows where
18-
# multiple Cycles repos are cloned side-by-side and all need the same
19-
# proxy. It is surprising if you only know about the per-repo .claude/
20-
# hook, so it is called out explicitly here.
21-
#
22-
# OPT-OUT: set CYCLES_CLAUDE_SKIP_REMOTE_REWRITE=1 to disable Part 2 entirely
23-
# (useful for local Claude Code runs where you do not want sibling repos
24-
# touched). Part 1 always runs.
25-
#
26-
# Issue: runcycles/.github#63
4+
# 1. Writes MCP deny rules to ~/.claude/settings.json so mcp__github__
5+
# file-mutation tools are blocked globally (even in cross-repo sessions).
6+
# 2. Fixes git remote URLs to use the local git proxy when available,
7+
# so native git push works instead of falling back to MCP tools.
278

289
set -e
2910

3011
# --- Part 1: Global MCP deny rules ---
3112

3213
GLOBAL_SETTINGS="$HOME/.claude/settings.json"
3314

34-
if ! [ -f "$GLOBAL_SETTINGS" ] || ! grep -q "mcp__github__push_files" "$GLOBAL_SETTINGS" 2>/dev/null; then
35-
mkdir -p "$HOME/.claude"
15+
# The previous version of this block only ran the merge when push_files was
16+
# missing, which silently left the policy incomplete if push_files happened to
17+
# exist while one of the other two rules had been removed. The python3 merge
18+
# is idempotent (skips rules already present), so we now always run it on
19+
# session start to guarantee all three deny rules are in place.
20+
# Tracked org-wide at runcycles/.github#63.
21+
mkdir -p "$HOME/.claude"
3622

37-
if [ -f "$GLOBAL_SETTINGS" ]; then
38-
TMP_SETTINGS=$(mktemp)
39-
if command -v python3 &>/dev/null; then
40-
python3 -c "
23+
if [ -f "$GLOBAL_SETTINGS" ]; then
24+
TMP_SETTINGS=$(mktemp)
25+
if command -v python3 &>/dev/null; then
26+
python3 -c "
4127
import json
4228
with open('$GLOBAL_SETTINGS') as f:
4329
settings = json.load(f)
@@ -56,11 +42,11 @@ with open('$TMP_SETTINGS', 'w') as f:
5642
json.dump(settings, f, indent=2)
5743
f.write('\n')
5844
" && mv "$TMP_SETTINGS" "$GLOBAL_SETTINGS"
59-
else
60-
rm -f "$TMP_SETTINGS"
61-
fi
6245
else
63-
cat > "$GLOBAL_SETTINGS" << 'EOF'
46+
rm -f "$TMP_SETTINGS"
47+
fi
48+
else
49+
cat > "$GLOBAL_SETTINGS" << 'EOF'
6450
{
6551
"$schema": "https://json.schemastore.org/claude-code-settings.json",
6652
"permissions": {
@@ -72,22 +58,22 @@ with open('$TMP_SETTINGS', 'w') as f:
7258
}
7359
}
7460
EOF
75-
fi
7661
fi
7762

78-
# --- Part 2: Fix git remote URLs to use local proxy (MULTI-REPO) ---
79-
# Some sessions clone repos via github.com directly, which lacks push credentials.
80-
# If the local git proxy is running, rewrite remote URLs to use it.
81-
#
82-
# WARNING: this part iterates every directory under /home/user/ and mutates the
83-
# `origin` remote of any github.com repo it finds, not just the current
84-
# checkout. See the file-level header for the rationale and opt-out.
85-
86-
if [ "${CYCLES_CLAUDE_SKIP_REMOTE_REWRITE:-}" = "1" ]; then
87-
# Opt-out path for local Claude Code runs that should not touch sibling repos.
63+
# --- Part 2: Fix git remote URLs to use local proxy ---
64+
# NOTE: This block intentionally rewrites the `origin` remote on EVERY sibling
65+
# repo under /home/user/* with a github.com remote, not just this one. Claude
66+
# Code remote sessions clone multiple repos and all need the local git proxy.
67+
# To opt out (e.g., when running outside that environment, or when you want
68+
# unrelated checkouts left alone), set CYCLES_CLAUDE_SKIP_REMOTE_REWRITE=1.
69+
# Tracked org-wide at runcycles/.github#63.
70+
if [ -n "$CYCLES_CLAUDE_SKIP_REMOTE_REWRITE" ]; then
8871
exit 0
8972
fi
9073

74+
# Some sessions clone repos via github.com directly, which lacks push credentials.
75+
# If the local git proxy is running, rewrite remote URLs to use it.
76+
9177
# Detect local git proxy: look for the proxy in any sibling repo's remote URL
9278
PROXY_BASE=""
9379
for dir in /home/user/*/; do

0 commit comments

Comments
 (0)