Summary
Implementing Grace’s self-improvement hooks required understanding the Claude Code hooks API precisely. Key finding: hooks receive JSON on stdin (not environment variables), stdout text gets injected as system context, and exit code 2 blocks the action. The right events for self-improvement areInstructionsLoaded (context injection), PostToolUseFailure (error capture), and SessionEnd (session logging).
What changed operationally
Discovered thatPostToolUseFailure (not PostToolUse with error checking) is the correct event for capturing failures — it only fires when a tool actually fails, avoiding false positives. InstructionsLoaded fires on session start and after context compaction, making it ideal for re-injecting learnings. SessionEnd fires on session termination.
Hook scripts must read stdin with cat and parse JSON via jq. The $CLAUDE_PROJECT_DIR variable resolves to the workspace root. Scripts in .claude/hooks/ need chmod +x.
Business impact
- Hooks work silently — no user friction, no prompts, no overhead
- Error capture is precise (only actual failures, not every Bash call)
- Context injection survives context compaction (re-fires on
InstructionsLoaded)
Operational takeaway
When writing Claude Code hooks: useset -euo pipefail but add || true after any grep that might return empty results (grep returns exit 1 on no match, which kills the script under set -e). Always exit 0 from capture hooks — exit 2 blocks the user’s action.