ring explicit plan re-injection or session rotation.
Core Solution
Prerequisites
- Claude Code installed and authenticated (
npm install -g @anthropic-ai/claude-code)
- A GitHub repository with
gh CLI authenticated
jq installed (for parsing hook payloads)
- Optional: Grass for mobile oversight of unattended sessions (
npm install -g @grass-ai/ide)
Step 1: Scaffold the Project Structure
mkdir -p .claude/hooks .claude/logs plans
Create the shared settings file that routes all hook calls:
// .claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "bash .claude/hooks/role-guard.sh" }
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "bash .claude/hooks/post-bash.sh" }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "bash .claude/hooks/on-stop.sh" }
]
}
]
}
}
Invoke each role by setting AGENT_ROLE in the environment. Hook scripts inherit this variable from the parent process:
AGENT_ROLE=architect claude -p "Your Architect task..."
AGENT_ROLE=engineer claude -p "Your Engineer task..."
AGENT_ROLE=reviewer claude -p "Your Reviewer task..."
role-guard.sh handles both the universal safety blocklist and per-role constraints in a single script:
#!/bin/bash
# .claude/hooks/role-guard.sh
TOOL_INPUT=$(cat)
COMMAND=$(echo "$TOOL_INPUT" | jq -r '.tool_input.command // empty')
ROLE="${AGENT_ROLE:-}"
block() {
echo "GATE BLOCKED: $1" >&2
exit 2
}
# ββ Universal blocklist: applies to every role ββββββββββββββββββββββββββββββ
DANGER='(git (reset --hard|clean -f|checkout \\.)|rm -rf|DROP TABLE)'
if echo "$COMMAND" | grep -qiP "$DANGER"; then
block "safety-guard: destructive operation requires manual approval"
fi
# ββ Role-specific constraints ββββββββββββββββββββββββββββββββββββββββββββββββ
case "$ROLE" in
architect)
if echo "$COMMAND" | grep -qP '(git (commit|push)|npm (run|test|build)|pytest)'; then
block "Architect constraint: write a plan doc in plans/ instead of executing code"
fi
;;
reviewer)
if echo "$COMMAND" | grep -qP '(git (commit|push|checkout -b)|\bsed -i\b)'; then
block "Reviewer constraint: read-only role β leave GitHub comments instead"
fi
;;
esac
echo '{"decision": "allow"}'
Note: block() exits 2 β Claude Code PreToolUse hooks use exit code 2 to block a specific tool call without aborting the session. The message goes to stderr so Claude Code surfaces it as the rejection reason. The universal blocklist runs before role checks so it cannot be bypassed by role misconfigurations.
Step 3: Trigger the Engineer from the Architect's Stop Hook
When the Architect session ends normally, on-stop.sh checks for a new plan file and spawns an Engineer:
#!/bin/bash
# .claude/hooks/on-stop.sh
ROLE="${AGENT_ROLE:-}"
PROJECT="$(pwd)"
case "$ROLE" in
architect)
PLAN_FILE=$(ls -t "$PROJECT/plans/"*.md 2>/dev/null | head -1)
if [[ -f "$PLAN_FILE" ]]; then
PLAN_NAME=$(basename "$PLAN_FILE" .md)
nohup env AGENT_ROLE=engineer claude \
-p "Implement the plan at $PLAN_FILE. Create branch feature/$PLAN_NAME. Open a PR when done. Do not modify files under plans/." \
>> "$PROJECT/.claude/logs/engineer.log" 2>&1 &
echo "Engineer spawned for $PLAN_FILE (PID: $!)"
fi
;;
esac
Always use absolute paths in nohup commands. Relative paths resolve against the working directory at spawn time, which may differ from the project root depending on how the Stop hook is invoked.
Step 4: Detect PR Creation and Spawn the Reviewer
The Engineer's PostToolUse hook watches bash outputs for GitHub PR URLs:
#!/bin/bash
# .claude/hooks/post-bash.sh
ROLE="${AGENT_ROLE:-}"
case "$ROLE" in
engineer)
TOOL_OUTPUT=$(cat)
PR_URL=$(echo "$TOOL_OUTPUT" \
| jq -r '.tool_output // empty' \
| grep -oP 'https://github\.com/[^\s]+/pull/\d+' | head -1)
if [[ -n "$PR_URL" ]]; then
# Dedup: don't spawn multiple reviewers for the same PR
LOCK="/tmp/reviewer-$(echo "$PR_URL" | md5sum | cut -c1-8).lock"
[[ -f "$LOCK" ]] && exit 0
touch "$LOCK"
nohup env AGENT_ROLE=reviewer claude \
-p "Review this PR critically. Check implementation against the plan in plans/. Identify bugs, missed requirements, and test gaps. Leave specific GitHub review comments: $PR_URL" \
>> "$(pwd)/.claude/logs/reviewer.log" 2>&1 &
fi
;;
esac
This is where the "they argue in pull request comments" behavior emerges. The Reviewer calls gh pr review --comment -b "..." with specific feedback. When the Engineer runs in a follow-up session, those review comments are in its context, and it addresses them in new commits.
Step 5: Implement the CEO Weekly Summarizer
Run the CEO agent via cron. It aggregates log tails and recent PR activity, then sends a summary:
#!/bin/bash
# .claude/hooks/ceo-weekly.sh
# Add to crontab: 0 9 * * 1 bash /path/to/.claude/hooks/ceo-weekly.sh
PROJECT="/absolute/path/to/your/project"
LOG_TAIL=$(tail -n 400 "$PROJECT/.claude/logs/"*.log 2>/dev/null)
PR_LIST=$(cd "$PROJECT" && gh pr list --state all --limit 20 \
--json number,title,state,createdAt 2>/dev/null)
env AGENT_ROLE=ceo claude --no-interactive \
-p "You are the CEO of an autonomous agent team. Based on the activity below, write a concise weekly summary: what shipped, what's in review, any anomalies. Send it as an email to admin@yourdomain.com.
AGENT LOGS:
$LOG_TAIL
RECENT PRS:
$PR_LIST"
The CEO role needs email capability configured (sendmail, a transactional API, or a custom tool). Keep its allowed-commands list tight β observe and report only.
Step 6: Safety Architecture & Verification
Before running unattended, enforce structural hard limits:
gh api repos/OWNER/REPO/branches/main/protection \
--method PUT \
--field enforce_admins=true \
--field required_pull_request_reviews='{"required_approving_review_count":1}' \
--field required_status_checks='{"strict":false,"contexts":[]}'
With this in place, no agent can merge to main regardless of what any hook permits. The Engineer opens PRs; merges require human approval or the Reviewer's explicit gh pr review --approve.
Smoke Test Verification:
# 1. Trigger the Architect with a minimal task
AGENT_ROLE=architect claude \
-p "Write a one-sentence plan for adding GET /healthz to an Express app. Save it to plans/healthz.md."
# 2. Confirm the plan was created
ls plans/
Pitfall Guide
- Hook Bypass via Multi-Step Destructive Paths:
PreToolUse only intercepts direct tool calls. Agents can chain benign commands to achieve destructive outcomes (e.g., downloading a script then executing it). Mitigation: Combine hook blocklists with filesystem permissions, containerized execution, and branch protection as a structural hard limit.
- Context Window Drift & Rule Ignorance: Claude agents consistently drift from system prompts past ~15 tool calls due to context pressure. Mitigation: Re-inject the plan document as context on every spawn, enforce session timeouts, and rotate agents before context saturation.
- Relative Path Resolution in
nohup Spawns: nohup inherits the working directory at spawn time, which may differ from the project root. Mitigation: Always use absolute paths for logs, plan files, and script invocations in on-stop.sh and post-bash.sh.
- Missing PR Deduplication (Lock File Race Conditions): Rapid successive
PostToolUse triggers can spawn duplicate Reviewer sessions. Mitigation: Implement MD5-based lock files in /tmp/ with atomic touch checks to guarantee exactly-once spawning per PR URL.
- Model Comprehension vs. Hook Enforcement (Scope Creep): Hooks enforce boundaries but cannot fix architectural misunderstandings. Agents may ignore PRDs or wire incorrect systems despite strict guardrails. Mitigation: Keep system prompts tight, validate plan documents before Engineer spawn, and mandate explicit
gh pr review --approve for merges.
- Insufficient Branch Protection: Relying solely on hooks for safety is fragile. A single bypass or misconfiguration can corrupt
main. Mitigation: Enforce GitHub branch protection rules programmatically via gh api. Require at least one approving review and disable direct pushes to protected branches.
Deliverables
- π Architecture Blueprint:
.claude/settings.json hook routing configuration with PreToolUse, PostToolUse, and Stop event mapping
- π Deployment Checklist:
- βοΈ Configuration Templates:
role-guard.sh (Universal + Role-specific constraints)
on-stop.sh (Architect β Engineer cascade)
post-bash.sh (PR detection β Reviewer spawn)
ceo-weekly.sh (Log aggregation + executive summary)
- π Safety Hardening Pack: Branch protection API payload, exit-code
2 blocking reference, and context drift mitigation patterns