Hooks
Automation triggers for events
Hooks run automatically in response to Claude Code events, enabling automation without manual invocation.
Hook Types
| Hook | Triggers When |
|---|---|
| Pre-command | Before any bash command runs |
| Post-command | After any bash command completes |
| Pre-commit | Before a git commit |
| Post-commit | After a git commit |
| Session start | When Claude session begins |
| Session stop | When Claude session ends |
Configuration
Hooks are defined in .claude/settings.json:
{
"hooks": {
"pre-commit": {
"command": "Rscript scripts/validate_pipeline_consistency.R"
},
"post-commit": {
"command": "./scripts/post_commit_review.sh"
},
"session-stop": {
"agent": "lab-reviewer"
}
}
}Common Hook Patterns
Pre-Commit Validation
Catch issues before they’re committed:
{
"hooks": {
"pre-commit": {
"command": "Rscript scripts/validate_pipeline_consistency.R",
"description": "Check globals.yml consistency"
}
}
}The validation script might check: - All globals.yml values are used correctly - No hardcoded values in manuscript - Data provenance is documented
Post-Commit Review
Automatically review after commits:
{
"hooks": {
"post-commit": {
"command": "./scripts/post_commit_review.sh",
"description": "Review changes after commit"
}
}
}Session Stop Review
Run a review when ending a session:
{
"hooks": {
"session-stop": {
"agent": "lab-reviewer",
"description": "Review session changes before exit"
}
}
}Pre-Slurm Checks
Validate before job submission:
{
"hooks": {
"pre-command": {
"pattern": "sbatch",
"command": "./scripts/preflight_check.sh",
"description": "Pre-submission validation"
}
}
}Writing Hook Scripts
Validation Script Example
# scripts/validate_pipeline_consistency.R
library(yaml)
# Load globals
globals <- read_yaml("config/globals.yml")
# Check R files for hardcoded values
r_files <- list.files("R/", pattern = "\\.R$", full.names = TRUE)
issues <- character()
for (file in r_files) {
content <- readLines(file)
# Check for hardcoded values that should be in globals
if (any(grepl("n_iterations\\s*=\\s*\\d+", content))) {
issues <- c(issues, paste(file, ": hardcoded n_iterations"))
}
}
if (length(issues) > 0) {
cat("Validation failed:\n")
cat(paste("-", issues), sep = "\n")
quit(status = 1)
} else {
cat("Validation passed\n")
}Review Script Example
#!/bin/bash
# scripts/post_commit_review.sh
SINCE_COMMIT=${1:-HEAD~1}
echo "Reviewing changes since $SINCE_COMMIT"
# Get changed files
CHANGED_FILES=$(git diff --name-only $SINCE_COMMIT HEAD)
# Check for critical file changes
CRITICAL="globals.yml _targets.R"
for file in $CRITICAL; do
if echo "$CHANGED_FILES" | grep -q "$file"; then
echo "WARNING: Critical file changed: $file"
fi
done
# Log the review
echo "$(date): Reviewed $CHANGED_FILES" >> logs/code_review.logHook Best Practices
Keep Hooks Fast
Hooks run synchronously. Slow hooks block Claude:
{
"hooks": {
"pre-commit": {
"command": "quick_check.sh", // Good: fast
"timeout": 10
}
}
}Make Hooks Informative
Output should explain what happened:
#!/bin/bash
echo "Running pre-commit checks..."
if ./check.sh; then
echo "✓ All checks passed"
else
echo "✗ Checks failed - commit blocked"
exit 1
fiHandle Failures Gracefully
Non-zero exit blocks the action:
# Return 0 to allow, non-zero to block
if (has_issues) {
cat("Issues found - blocking commit\n")
quit(status = 1) # Block
} else {
quit(status = 0) # Allow
}Log Everything
Keep a record of hook executions:
LOG_FILE="logs/hooks.log"
echo "$(date): $HOOK_NAME executed" >> $LOG_FILEDebugging Hooks
Check hook configuration
> What hooks are configured for this project?
Test hook manually
# Run the hook script directly
./scripts/post_commit_review.sh
# Check exit code
echo $? # 0 = success, non-zero = failureDisable temporarily
> Run this command without hooks
Or edit .claude/settings.json to comment out the hook.
Session Start Hooks
Run setup tasks or reminders when Claude starts a session. These are useful for environment checks, status displays, and context reminders.
Reminder to review CLAUDE.md:
{
"hooks": {
"SessionStart": [{
"command": "echo 'Reminder: Review CLAUDE.md if project context has changed'"
}]
}
}Check for uncommitted work from last session:
{
"hooks": {
"SessionStart": [{
"command": "bash -c 'CHANGES=$(git status --porcelain | wc -l); if [ $CHANGES -gt 0 ]; then echo \"⚠ $CHANGES uncommitted changes from last session\"; fi'",
"description": "Warn about uncommitted changes on session start"
}]
}
}Display pipeline status on startup:
{
"hooks": {
"SessionStart": [{
"command": "Rscript -e 'p <- targets::tar_progress(); cat(sprintf(\"Pipeline: %d complete, %d errored, %d dispatched\\n\", sum(p$progress==\"completed\"), sum(p$progress==\"errored\"), sum(p$progress==\"dispatched\")))'",
"description": "Show targets pipeline status on session start"
}]
}
}Check for updates to a plugin or tool:
{
"hooks": {
"SessionStart": [{
"command": "node scripts/check-updates.js",
"description": "Background check for tool updates"
}]
}
}SessionStart hooks run before you can interact with Claude. Keep them under 2-3 seconds. For slower checks, use background processes that write results to a file Claude can read later.
Lab-Recommended Hooks
Research Projects
{
"hooks": {
"pre-commit": {
"command": "Rscript scripts/validate_pipeline_consistency.R"
},
"SessionStart": [{
"command": "bash -c 'CHANGES=$(git status --porcelain | wc -l); if [ $CHANGES -gt 0 ]; then echo \"⚠ $CHANGES uncommitted changes from last session\"; fi'"
}],
"session-stop": {
"agent": "lab-reviewer"
}
}
}Methods Papers
{
"hooks": {
"pre-commit": {
"command": "Rscript scripts/check_manuscript_consistency.R"
},
"post-commit": {
"command": "./scripts/post_commit_review.sh"
},
"SessionStart": [{
"command": "echo 'Reminder: Run make validate before submitting manuscript'"
}]
}
}Caution
Hooks with bugs can block all operations. Test thoroughly:
- Run hook script manually first
- Test with a dummy commit
- Have a way to disable (edit settings.json)