chore(config): centralize git for teammates and enable tmux panes

Two agent-team tooling changes:

- teammateMode: in-process -> auto. Teammates now spawn in tmux split panes
  when the lead runs inside tmux, with graceful in-process fallback.
- New PreToolUse hook git-centralize-guard.sh blocks .git-mutating commands
  (add, commit, merge, push, pull, rebase, reset, checkout, stash,
  cherry-pick, rm, mv) for teammates, keeping version control centralized to
  the lead. Detection keys on the agent_type field, which a teammate's hook
  input carries and the lead's does not. Read-only git is allowed.

Documented in .claude/rules/git-safety.md. Hooks load at lead startup, so a
restart is required for the hook to reach teammates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-22 12:37:47 +02:00
co-authored by Claude Opus 4.7
parent a0097caee7
commit 0127fc8fd9
3 changed files with 55 additions and 1 deletions
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# git-centralize-guard: PreToolUse hook that blocks .git-mutating commands for
# agent-team teammates. Version control is centralized to the team lead
# (see CLAUDE.md / memory: "Team lead commits, never agents").
#
# Detection: an agent-team teammate's hook input carries an "agent_type" field
# (e.g. "qatux"); the team lead's main session does NOT. So agent_type present
# => a teammate (or subagent) => block git writes. Absent => the lead => allow.
# (Empirically confirmed on Claude Code 2.1.148; docs field is "agent_type".)
#
# Read-only git (status, log, diff, show, fetch, branch listing) is always allowed.
set -uo pipefail
INPUT=$(cat)
# Only act on Bash tool calls
TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_name',''))" 2>/dev/null)
[ "$TOOL_NAME" = "Bash" ] || exit 0
# Teammates/subagents carry agent_type; the lead does not.
AGENT_TYPE=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('agent_type','') or '')" 2>/dev/null)
[ -n "$AGENT_TYPE" ] || exit 0 # lead -> allow everything
COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null)
# Block git history/working-tree-mutating subcommands anywhere in the command
# (catches chained forms like `cd x && git commit`). Read-only git is allowed.
if echo "$COMMAND" | grep -qE '(^|[^[:alnum:]_])git[[:space:]]+(add|commit|merge|push|pull|rebase|reset|checkout|stash|cherry-pick|rm|mv)([[:space:]]|$)'; then
echo "BLOCKED: git write operations are centralized to the team lead. Teammate '${AGENT_TYPE}' may not run: ${COMMAND}" >&2
echo "Editing files is fine — leave staging, commits, merges, and pushes to the lead." >&2
exit 2
fi
exit 0
+15
View File
@@ -7,6 +7,21 @@
- Skip files in `.gitignore` - Skip files in `.gitignore`
- The `.claude/` directory IS tracked — skills and agents belong in the repo - The `.claude/` directory IS tracked — skills and agents belong in the repo
## Centralized version control (agent teams)
Git is the **team lead's** job. Teammates edit files; the lead stages, commits,
merges, and pushes. This is enforced, not just convention:
- **Hook:** `.claude/hooks/git-centralize-guard.sh` (PreToolUse on Bash) blocks
`.git`-mutating commands — `add`, `commit`, `merge`, `push`, `pull`, `rebase`,
`reset`, `checkout`, `stash`, `cherry-pick`, `rm`, `mv` — for any agent-team
teammate. Read-only git (`status`, `log`, `diff`, `show`, `fetch`) is allowed.
- **Detection:** a teammate's PreToolUse hook input carries an `agent_type` field
(e.g. `"qatux"`); the lead's main session does not. Present → block; absent →
allow. (Confirmed on Claude Code 2.1.148.)
- **Activation:** hooks load at lead startup and teammates inherit that snapshot,
so changes to this hook require a Claude Code restart to reach teammates.
## Commit conventions ## Commit conventions
Use conventional commits: `<type>(<scope>): <summary>` Use conventional commits: `<type>(<scope>): <summary>`
+6 -1
View File
@@ -4,7 +4,7 @@
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1", "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1",
"SR_DB_PATH": "/var/home/jeroenschweitzer/Projects/settled-reach/settledreach.db" "SR_DB_PATH": "/var/home/jeroenschweitzer/Projects/settled-reach/settledreach.db"
}, },
"teammateMode": "in-process", "teammateMode": "auto",
"permissions": { "permissions": {
"allow": [ "allow": [
"Bash(git add *)", "Bash(git add *)",
@@ -87,6 +87,11 @@
"type": "command", "type": "command",
"command": ".claude/hooks/git-lock-guard.sh", "command": ".claude/hooks/git-lock-guard.sh",
"timeout": 5 "timeout": 5
},
{
"type": "command",
"command": ".claude/hooks/git-centralize-guard.sh",
"timeout": 5
} }
] ]
} }