Welcome to the Claude Pilot project! This is the first chapter of our journey. Before we build complex agents, we need to understand how we control them.
Imagine you hire a junior developer. They are fast and enthusiastic, but sometimes they:
rm -rf) without asking.You wouldn't let them work without supervision. Similarly, Claude Pilot acts as a supervisor for the Claude Code CLI. It ensures that every action the AI takes adheres to your project's standards automatically.
The Lifecycle Hooks System is the "nervous system" of Claude Pilot. It sits invisibly between you and the AI.
Think of it like a series of security checkpoints or quality assurance gates. When Claude tries to do something (like write a file or run a command), the system pauses and checks a list of rules defined in a configuration file.
PreToolUse is before an action, PostToolUse is after).Bash tool or Write tool).Let's look at a concrete example used in this project: The TDD Enforcer. We want to ensure that whenever Claude modifies code, it also writes a test for it. If it forgets, we want to remind it immediately.
hooks.json)First, we tell Claude Pilot to listen for file changes. We add this to our configuration file.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "python hooks/tdd_enforcer.py"
}
]
}
]
}
}
What this does:
PostToolUse: Wait until after Claude has successfully written or edited a file.matcher: Only trigger if the tool used was Write or Edit.command: Run our custom tdd_enforcer.py script.tdd_enforcer.py)Now, let's look at the script that runs. The script communicates with Claude Pilot using Standard Input (stdin) and Exit Codes.
Claude Pilot sends details about what just happened (the file path, the code written) via JSON into the script's input.
import sys
import json
# Read the event data sent by Claude Pilot
try:
hook_data = json.load(sys.stdin)
tool_input = hook_data.get("tool_input", {})
file_path = tool_input.get("file_path", "")
except:
sys.exit(0) # Exit cleanly if data is bad
We check if the file is code (e.g., .py or .ts) and if a corresponding test file exists.
# Simplified logic check
def check_test_exists(file_path):
# If editing a test, we are good!
if "test" in file_path:
return True
# Check if 'test_myfile.py' exists
# (Implementation details skipped for brevity)
return False
This is the most important part.
if not check_test_exists(file_path):
print("TDD Reminder: You modified code but no test exists.", file=sys.stderr)
print("Please create a test file before continuing.", file=sys.stderr)
# Exit 2 tells Claude: "Stop! Read the error above."
sys.exit(2)
# Exit 0 means: "Carry on."
sys.exit(0)
Let's visualize the flow when Claude tries to edit main.py without a test.
PreToolUse)
We can also block actions before they happen. In pilot/hooks/tool_redirect.py, we prevent Claude from using WebSearch because we want it to use a specific project tool instead.
# From pilot/hooks/tool_redirect.py
def block_web_search():
print("โ WebSearch is blocked.", file=sys.stderr)
print("Please use the 'ToolSearch' alternative instead.", file=sys.stderr)
return 2 # Exit code 2 blocks the action BEFORE it runs
In hooks.json, this is configured under PreToolUse. If the script returns exit code 2 here, the WebSearch tool never actually executes!
The Lifecycle Hooks System is a powerful way to automate governance.
hooks.json.By using this system, we don't have to manually check every line of code Claude writes. The system does it for us.
In the next chapter, we will learn about the Worker Daemon, which allows these hooks to perform complex background tasks without slowing Claude down.
Next: Worker Daemon (Service Orchestrator)
Generated by Code IQ