Project 1: Hook Hello World - Session Greeter
Project 1: Hook Hello World - Session Greeter
Project Overview
| Attribute | Value |
|---|---|
| Project Number | 1 of 40 |
| Category | Hooks System Mastery |
| Main Programming Language | Bash |
| Alternative Languages | Python, JavaScript (Bun), Go |
| Difficulty Level | Level 1: Beginner |
| Time Estimate | 2-4 hours |
| Coolness Level | Level 2: Practical but Forgettable |
| Business Potential | Resume Gold |
| Knowledge Area | Hooks / Event-Driven Architecture |
| Primary Tool | Claude Code Hooks System |
| Main Reference | âWicked Cool Shell Scriptsâ by Dave Taylor |
Summary: Build a SessionStart hook that greets you with the current date, weather, and a motivational quote when you start a Claude session. This is your introduction to the Claude Code hooks system.
Real World Outcome
When you start any Claude Code session, you will see a personalized greeting:
$ claude
+--------------------------------------------------+
| SESSION GREETER |
+--------------------------------------------------+
Good morning, Douglas!
Today: Sunday, December 22, 2025
Weather: San Francisco - 58 F, Partly Cloudy
"The only way to do great work is to love what you do."
- Steve Jobs
+--------------------------------------------------+
Starting Claude Code session...
This hook runs every time Claude starts, giving you contextual awareness before diving into work. It demonstrates that you can inject custom behavior at precise points in Claudeâs lifecycle.
What This Teaches You
- How hooks are configured in settings.json
- The SessionStart event and when it fires
- Reading JSON from stdin in shell scripts
- Exit codes and their meaning in hook context
- Making external API calls from hooks
The Core Question Youâre Answering
âHow do I make Claude Code do something automatically when specific events happen?â
Before you write any code, understand this fundamental concept:
Hooks are the deterministic backbone of Claude Code automation. Unlike tool use (which Claude decides when to invoke), hooks fire predictably on specific events. This predictability is what makes them powerful for:
- Security enforcement (blocking dangerous operations)
- Workflow automation (auto-formatting, notifications)
- State management (session persistence)
- Developer experience (greetings, context enrichment)
HOOK vs TOOL INVOCATION
+-----------------+ +-----------------+
| HOOKS | | TOOLS |
+-----------------+ +-----------------+
| Deterministic | | Non-deterministic|
| Event-triggered | | Model-decided |
| Your code runs | | Claude decides |
| Always fires | | May or may not |
+-----------------+ +-----------------+
| |
v v
"SessionStart" vs "Claude, read this file"
ALWAYS runs your Claude chooses
script when session whether to use
begins Read tool
Concepts You Must Understand First
Stop and research these before coding:
1. Hook Event Types
Claude Code has 10+ hook events. For this project, focus on SessionStart:
| Event | When It Fires | Use Case |
|---|---|---|
SessionStart |
When Claude session begins | Initialize environment, greet user |
UserPromptSubmit |
Before prompt reaches Claude | Validate/transform input |
PreToolUse |
Before any tool executes | Block dangerous operations |
PostToolUse |
After tool completes | Format, log, notify |
Stop |
When session ends | Cleanup, save state |
Notification |
Claude needs attention | Alert user |
PreCompact |
Before context compression | Save important state |
SubagentStop |
Subagent completes | Collect results |
Reference: Claude Code Docs - âHooksâ section
2. Hook Configuration Schema
Hooks are configured in settings.json with this structure:
{
"hooks": [
{
"event": "SessionStart",
"type": "command",
"command": "/path/to/your/script.sh",
"timeout": 10000
}
]
}
| Field | Required | Description |
|---|---|---|
event |
Yes | Which lifecycle event triggers this hook |
type |
Yes | âcommandâ (shell) or âpromptâ (Haiku LLM) |
command |
Yes* | Path to script (for type: âcommandâ) |
timeout |
No | Max execution time in ms (default: varies by event) |
Where hooks live:
~/.claude/settings.json- Personal hooks (all projects).claude/settings.json- Project-specific hooks (team-shared).claude/settings.local.json- Project-specific, not in git
Reference: Claude Code Docs - âHooks Configurationâ
3. Exit Codes and Control Flow
Exit codes are how hooks communicate with Claude:
+-------------+ +-------------+ +-------------+
| Exit 0 | | Exit 2 | | Other Exit |
+-------------+ +-------------+ +-------------+
| SUCCESS | | BLOCK | | ERROR |
| Allow action| | Deny action | | Log warning |
| Continue | | Stop action | | Continue |
+-------------+ +-------------+ +-------------+
For SessionStart:
- Exit 0: Session starts normally
- Exit 2: Session is aborted (useful for environment checks)
- Other: Warning logged, session continues
Reference: âThe Linux Command Lineâ Ch. 24 by William Shotts
Questions to Guide Your Design
Before implementing, think through these design decisions:
1. Event Selection
- Which event fires the greeting? SessionStart is the only choice here
- Should this block Claude if it fails? Probably not - a failed greeting shouldnât prevent work
- What happens if the weather API is down? Your hook should handle this gracefully
2. Data Fetching
- How will you get weather data?
- Option A:
curl -s "wttr.in/YourCity?format=3"(simple, no API key) - Option B: OpenWeatherMap API (more reliable, needs key)
- Option C: Local weather cache (fastest, may be stale)
- Option A:
- How will you get a motivational quote?
- Option A:
fortunecommand (if installed) - Option B: Hardcoded array of quotes
- Option C: Quote API (adds latency)
- Option A:
- Should fetching happen synchronously or async?
- Sync: Simpler, but delays session start
- Async: Faster start, but output may appear late
3. Configuration
- Should the greeting be customizable? Consider user preferences
- How will you handle different timezones? System time vs explicit config
- Should users be able to disable it? Environment variable toggle?
Thinking Exercise
Trace the Hook Execution
Before coding, trace what happens when you run claude:
USER TERMINAL CLAUDE CODE INTERNALS
+--------------+ +------------------------+
| | | |
| $ claude | | 1. Parse CLI args |
| | ----------------> | |
| | | 2. Load settings.json |
| | | |
| | | 3. Find SessionStart |
| | | hooks |
| | | |
| | | 4. For each hook: |
| | | a. Spawn shell |
| | | b. Pipe JSON stdin |
| | <-------------| c. Wait (timeout) |
| [Greeting | | d. Check exit code |
| appears] | | |
| | | 5. Start REPL |
| | | |
+--------------+ +------------------------+
JSON Payload on stdin:
{
"hook_event_name": "SessionStart",
"session_id": "abc123-def456-ghi789",
"cwd": "/Users/douglas/projects/myapp",
"timestamp": "2025-12-22T10:30:00Z"
}
Questions While Tracing:
- What data is available in the stdin JSON?
- session_id, cwd (current working directory), timestamp
- What happens if your script takes too long?
- Hook times out (default ~10s), warning logged, session continues
- Can you output to the terminal from a hook?
- Yes! stdout goes to terminal, stderr goes to logs
The Interview Questions Theyâll Ask
Prepare to answer these questions about hooks:
1. âHow would you automate a task that needs to run every time a developer starts their AI coding assistant?â
Answer: Use a SessionStart hook. Configure it in settings.json with the event type âSessionStartâ, point it to a shell script, and ensure it exits with code 0 for normal operation. The script receives session metadata via stdin as JSON.
2. âExplain the difference between a hook and a tool in Claude Code.â
Answer:
- Hooks are deterministic, event-driven scripts that run at specific lifecycle points. YOU control when they fire.
- Tools are capabilities Claude can invoke (Read, Write, Bash). CLAUDE decides when to use them.
- Hooks run your code; tools run Claudeâs built-in capabilities.
3. âWhatâs the security implication of exit code 2 in hooks?â
Answer: Exit code 2 is a hard block. For PreToolUse hooks, it prevents the tool from executing entirely. This creates a deterministic security boundary that even the user cannot override without modifying the hook configuration. Itâs useful for protecting sensitive files or preventing dangerous operations.
4. âHow would you debug a hook thatâs not firing?â
Answer:
- Check file is executable:
chmod +x script.sh - Validate settings.json is valid JSON:
jq . settings.json - Verify event name is exact: âSessionStartâ not âsessionStartâ
- Check hook timeout hasnât passed
- Run script manually:
echo '{}' | ./script.sh - Check stderr output in Claude logs
5. âCan hooks modify Claudeâs behavior, or only perform side effects?â
Answer: Mostly side effects, but some hooks can modify behavior:
- UserPromptSubmit: Can modify the prompt Claude sees
- PreToolUse: Can block tool execution
- SessionStart: Can set environment variables via CLAUDE_ENV_FILE Most hooks (PostToolUse, Stop, Notification) are for side effects only.
Hints in Layers
Use these hints progressively when stuck:
Hint 1: Starting Point
Create a file at ~/.claude/hooks/session-greeter.sh and make it executable:
mkdir -p ~/.claude/hooks
touch ~/.claude/hooks/session-greeter.sh
chmod +x ~/.claude/hooks/session-greeter.sh
Hint 2: Configuration
Add a hook entry to ~/.claude/settings.json:
{
"hooks": [
{
"event": "SessionStart",
"type": "command",
"command": "~/.claude/hooks/session-greeter.sh"
}
]
}
Hint 3: Script Structure
Your script should follow this pattern:
#!/bin/bash
# 1. Read stdin (even if you don't use it - prevent broken pipe)
cat > /dev/null
# 2. Print greeting to stdout (appears in terminal)
echo "Hello, $(whoami)!"
# 3. Exit with code 0 (success - allow session to continue)
exit 0
Hint 4: Debugging
If the hook doesnât fire:
- Test manually:
echo '{"session_id": "test"}' | ~/.claude/hooks/session-greeter.sh - Check Claude logs:
~/.claude/logs/for errors - Verify settings.json path matches your actual file location
- Try absolute path instead of
~expansion
Books That Will Help
| Topic | Book | Chapter | Why It Helps |
|---|---|---|---|
| Shell scripting basics | âThe Linux Command Lineâ by William Shotts | Ch. 24 | Understanding exit codes, scripts, stdin/stdout |
| JSON processing in bash | âjq Manualâ | Filters section | Parsing the JSON payload hooks receive |
| Event-driven patterns | âDesigning Event-Driven Systemsâ by Ben Stopford | Ch. 1 | Understanding event-driven architecture concepts |
| Cool shell scripts | âWicked Cool Shell Scriptsâ by Dave Taylor | Ch. 2-4 | Practical bash scripting patterns |
| Clean code principles | âClean Codeâ by Robert C. Martin | Ch. 2-3 | Writing readable, maintainable scripts |
Implementation Guide
Step 1: Create the Hook Script
#!/bin/bash
# ~/.claude/hooks/session-greeter.sh
# SessionStart hook that displays a personalized greeting
# Consume stdin to prevent broken pipe errors
cat > /dev/null
# Get current user
USER_NAME=$(whoami)
# Get time of day for appropriate greeting
HOUR=$(date +%H)
if [ $HOUR -lt 12 ]; then
GREETING="Good morning"
elif [ $HOUR -lt 17 ]; then
GREETING="Good afternoon"
else
GREETING="Good evening"
fi
# Get current date
DATE_STR=$(date "+%A, %B %d, %Y")
# Get weather (with timeout to prevent slow starts)
# Uses wttr.in - a free weather service that returns plain text
CITY="${CLAUDE_GREETER_CITY:-San Francisco}"
WEATHER=$(timeout 3 curl -s "wttr.in/${CITY}?format=%l:+%t,+%C" 2>/dev/null || echo "Weather unavailable")
# Array of motivational quotes
QUOTES=(
"The only way to do great work is to love what you do.|Steve Jobs"
"Code is like humor. When you have to explain it, it is bad.|Cory House"
"First, solve the problem. Then, write the code.|John Johnson"
"The best error message is the one that never shows up.|Thomas Fuchs"
"Simplicity is the soul of efficiency.|Austin Freeman"
"Make it work, make it right, make it fast.|Kent Beck"
)
# Pick a random quote
QUOTE_INDEX=$((RANDOM % ${#QUOTES[@]}))
QUOTE_LINE="${QUOTES[$QUOTE_INDEX]}"
QUOTE_TEXT=$(echo "$QUOTE_LINE" | cut -d'|' -f1)
QUOTE_AUTHOR=$(echo "$QUOTE_LINE" | cut -d'|' -f2)
# Print the greeting
echo ""
echo "+--------------------------------------------------+"
echo "| SESSION GREETER |"
echo "+--------------------------------------------------+"
echo ""
echo " $GREETING, $USER_NAME!"
echo ""
echo " Today: $DATE_STR"
echo " Weather: $WEATHER"
echo ""
echo " \"$QUOTE_TEXT\""
echo " - $QUOTE_AUTHOR"
echo ""
echo "+--------------------------------------------------+"
echo ""
# Exit 0 to allow session to continue
exit 0
Step 2: Configure settings.json
{
"hooks": [
{
"event": "SessionStart",
"type": "command",
"command": "~/.claude/hooks/session-greeter.sh",
"timeout": 5000
}
]
}
Step 3: Make It Configurable
Create ~/.claude/greeter-config.json:
{
"city": "San Francisco",
"show_weather": true,
"show_quote": true,
"custom_greeting": null
}
Enhanced script that reads config:
#!/bin/bash
# Read config if it exists
CONFIG_FILE="$HOME/.claude/greeter-config.json"
if [ -f "$CONFIG_FILE" ]; then
CITY=$(jq -r '.city // "San Francisco"' "$CONFIG_FILE")
SHOW_WEATHER=$(jq -r '.show_weather // true' "$CONFIG_FILE")
SHOW_QUOTE=$(jq -r '.show_quote // true' "$CONFIG_FILE")
else
CITY="San Francisco"
SHOW_WEATHER=true
SHOW_QUOTE=true
fi
# ... rest of script uses these variables
Architecture Diagram
+-----------------------------------------------------------------------+
| HOOK EXECUTION FLOW |
+-----------------------------------------------------------------------+
| |
| +-------------------+ |
| | $ claude | |
| +--------+----------+ |
| | |
| v |
| +-------------------+ |
| | Claude Code Init | |
| +--------+----------+ |
| | |
| v |
| +-------------------+ +------------------+ |
| | Load settings.json|--->| hooks: [ | |
| +--------+----------+ | { | |
| | | event: | |
| | | "SessionStart"| |
| | | command: ... | |
| | | } | |
| | | ] | |
| v +------------------+ |
| +-------------------+ |
| | Fire SessionStart | |
| +--------+----------+ |
| | |
| | spawn process |
| v |
| +---------------------------------------+ |
| | session-greeter.sh | |
| | +----------------------------------+ | |
| | | stdin: {"session_id": "...", | | |
| | | "cwd": "/path/..."} | | |
| | +----------------------------------+ | |
| | | |
| | 1. Read stdin (consume) | |
| | 2. Fetch weather (curl wttr.in) | |
| | 3. Select random quote | |
| | 4. Print greeting to stdout | |
| | 5. exit 0 | |
| +---------------------------------------+ |
| | |
| | exit code 0 |
| v |
| +-------------------+ |
| | Session Continues | |
| +-------------------+ |
| |
+-----------------------------------------------------------------------+
Learning Milestones
Track your progress through these checkpoints:
Milestone 1: Hook Fires on Session Start
Goal: See any output when starting Claude
Test: Run claude and verify your greeting appears
What Youâve Learned:
- Hook configuration in settings.json
- Event binding to SessionStart
- Basic shell script structure
Milestone 2: Weather Displays Correctly
Goal: Dynamic weather from wttr.in appears
Test: Weather should match your location
What Youâve Learned:
- Making HTTP calls from hooks
- Handling API timeouts gracefully
- String formatting in bash
Milestone 3: Greeting is Customizable
Goal: Change city or greeting without editing script
Test: Set CLAUDE_GREETER_CITY=London and verify
What Youâve Learned:
- Environment variable integration
- External configuration files
- Separation of config from code
Common Mistakes to Avoid
Mistake 1: Forgetting to Consume stdin
# WRONG - may cause broken pipe
#!/bin/bash
echo "Hello!"
exit 0
# RIGHT - always consume stdin
#!/bin/bash
cat > /dev/null # Consume stdin
echo "Hello!"
exit 0
Mistake 2: Not Making Script Executable
# After creating the script:
chmod +x ~/.claude/hooks/session-greeter.sh
Mistake 3: Using Exit Code 2 Accidentally
# WRONG - this blocks the session!
if [ -z "$WEATHER" ]; then
exit 2 # Oops! Session won't start
fi
# RIGHT - exit 0 even on partial failure
if [ -z "$WEATHER" ]; then
WEATHER="unavailable"
fi
exit 0
Mistake 4: Slow API Calls Without Timeout
# WRONG - could hang forever
curl -s "https://slow-api.example.com/weather"
# RIGHT - use timeout
timeout 3 curl -s "https://api.example.com/weather" || echo "fallback"
Extension Ideas
Once you have the basic greeter working, try these enhancements:
- Project-Aware Greeting: Read the current directory and show relevant project info
- Git Status Integration: Show branch name and pending changes
- Calendar Integration: Show upcoming meetings or deadlines
- System Health: Display CPU/memory/disk status
- Custom Themes: Allow different visual styles for the greeting
Summary
This project introduced you to the Claude Code hooks system through the simplest possible example: a SessionStart hook that displays a greeting. You learned:
- Hook Events: When and why different events fire
- Configuration: How to set up hooks in settings.json
- Exit Codes: How hooks communicate success/failure
- Shell Scripting: Reading stdin, making API calls, outputting to terminal
With this foundation, youâre ready to tackle more complex hooks like PreToolUse (Project 2) and PostToolUse (Project 3).