The SDK documentation incorrectly stated that hooks receive data through the $HOOK_INPUT environment variable. Testing revealed this variable was always empty, preventing hooks from working properly.
Through deep analysis of the Claude Code source code and extensive testing, we discovered that hooks actually receive data via STDIN as JSON. This is a fundamental correction to how hooks should be implemented.
#!/usr/bin/env ruby
require 'json'
# WRONG: This doesn't work
hook_input = ENV['HOOK_INPUT']
data = JSON.parse(hook_input) if hook_input#!/usr/bin/env ruby
require 'json'
# CORRECT: Read from STDIN
data = JSON.parse(STDIN.read)- README.md - Corrected hooks documentation with STDIN examples
- AI_SUMMARY.md - Updated with critical finding about STDIN
- CLAUDE_CODE_ANALYSIS.md - Added comprehensive analysis of hooks mechanism
- CHANGELOG.md - Documented breaking changes
- lib/claude_code_sdk/version.rb - Bumped to 0.4.0
- examples/hooks_stdin_reader.rb - Basic hook reading from STDIN
- examples/allspark_webhook_hook.rb - Complete webhook integration
- examples/inline_hooks.rb - Inline commands for direct use
- test_hooks_simple.rb - Simplified test demonstrating STDIN approach
- test_hooks_v4.rb - Comprehensive test suite for v0.4.0
# Read hook data
data = JSON.parse(STDIN.read)
# Access hook information
hook_event = data['hook_event_name']
tool_name = data['tool_name']
session_id = data['session_id']
tool_input = data['tool_input']import json
import sys
# Read hook data
data = json.load(sys.stdin)
# Access hook information
hook_event = data.get('hook_event_name')
tool_name = data.get('tool_name')#!/bin/bash
# Read JSON from STDIN
hook_data=$(cat)
# Process with jq or other tools
echo "$hook_data" | jq '.tool_name'-
Hooks don't work with
--printflag: The SDK uses--printfor output formatting, which disables hooks in the CLI. This is a Claude Code CLI limitation, not an SDK bug. -
Workaround Options:
- Use hooks for monitoring/logging in production (without --print)
- Implement SDK-level hooks that process messages after receiving them
- Use the CLI directly for operations that require hooks
Replace all instances of ENV['HOOK_INPUT'] with STDIN.read:
# Old (broken)
data = JSON.parse(ENV['HOOK_INPUT'] || '{}')
# New (working)
data = JSON.parse(STDIN.read)Ensure your hooks configuration uses the correct command format:
hooks = {
"hooks" => {
"PostToolUse" => [
{
"matcher" => ".*",
"hooks" => [
{
"type" => "command",
"command" => "ruby /path/to/hook.rb" # Hook must read from STDIN
}
]
}
]
}
}Use the provided test files to verify your hooks work correctly:
ruby test_hooks_simple.rb- Full hooks documentation: See README.md "Hooks (Advanced)" section
- Implementation examples: See examples/hooks_stdin_reader.rb
- Webhook integration: See examples/allspark_webhook_hook.rb
- Claude Code analysis: See CLAUDE_CODE_ANALYSIS.md
Version 0.4.0 represents a critical fix to the SDK's hooks implementation. While the hooks configuration and setup were correct, the documentation about how hooks receive data was fundamentally wrong. This release corrects that misconception and provides working examples that properly read from STDIN.
This discovery came from deep analysis of the Claude Code source code and extensive testing, demonstrating the importance of verifying assumptions against actual implementation rather than relying solely on documentation.
Special thanks to the thorough testing and analysis that revealed this critical issue. The SDK is now correctly documented and developers can successfully implement hooks following the examples provided.