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
289set -e
2910
3011# --- Part 1: Global MCP deny rules ---
3112
3213GLOBAL_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 "
4127import json
4228with 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}
7460EOF
75- fi
7661fi
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
8972fi
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
9278PROXY_BASE=" "
9379for dir in /home/user/* /; do
0 commit comments