[BUG] Hooks Completely Non-Functional in Subdirectories (v2.0.27) - Blocking CI/CD Workflows
[BUG] Hooks Completely Non-Functional in Subdirectories (v2.0.27) - Blocking CI/CD Workflows
Environment
- Claude Code Version: v2.0.27
- Platform: macOS Darwin 23.5.0
- Test Directory:
/tmp/test-project/workspace/subdir(subdirectory) - Settings Files Tested:
~/.claude/settings.json(global)/tmp/test-project/.claude/settings.json(project root)/tmp/test-project/workspace/subdir/.claude/settings.json(working directory)
Problem Statement
All hook types (UserPromptSubmit, SessionStart, PreToolUse, PostToolUse, Stop) completely fail to execute when Claude Code runs from subdirectories. This blocks development of advanced CI/CD pipelines that rely on hooks to orchestrate automated workflows.
This appears to be a combination of Issue #8810 (subdirectory bug) and Issue #6305 (PreToolUse/PostToolUse broken), making hooks entirely unusable in real-world project structures where Claude Code is typically launched from nested directories.
Automated Reproduction Script
Run this script to reproduce the bug in a clean environment:
#!/usr/bin/env bash
# claude-hooks-reproduction-test.sh
set -e
echo "🧪 Claude Code Hooks Bug Reproduction Test"
echo "=========================================="
echo ""
# Setup test directory structure
TEST_DIR="/tmp/claude-hooks-test-$$"
mkdir -p "$TEST_DIR/workspace/subdir"
cd "$TEST_DIR"
# Create hook script
mkdir -p "$TEST_DIR/workspace/subdir/.claude/hooks"
cat > "$TEST_DIR/workspace/subdir/.claude/hooks/test-hook.sh" <<'HOOK_EOF'
#!/usr/bin/env bash
read -r input_json
echo "🎬 Hook executed! $(date)" >> /tmp/claude-hook-test.log
echo "🎬 Hook fired!" >&2
exit 0
HOOK_EOF
chmod +x "$TEST_DIR/workspace/subdir/.claude/hooks/test-hook.sh"
# Test 1: Local settings.json (working directory)
echo "Test 1: Local .claude/settings.json in working directory"
cat > "$TEST_DIR/workspace/subdir/.claude/settings.json" <<'SETTINGS_EOF'
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "/tmp/claude-hooks-test-$$/workspace/subdir/.claude/hooks/test-hook.sh"
}
]
}
]
}
}
SETTINGS_EOF
# Replace $$ with actual PID in the script path
sed -i '' "s/\\$\\$/$$/g" "$TEST_DIR/workspace/subdir/.claude/settings.json"
# Test 2: Global settings.json
echo "Test 2: Global ~/.claude/settings.json"
mkdir -p ~/.claude
cp ~/.claude/settings.json ~/.claude/settings.json.backup 2>/dev/null || true
cat > ~/.claude/settings.json <<GLOBAL_EOF
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$TEST_DIR/workspace/subdir/.claude/hooks/test-hook.sh"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "cat > /dev/null && date '+Stop: %Y-%m-%d %H:%M:%S' >> /tmp/claude-hook-test.log"
}
]
}
]
}
}
GLOBAL_EOF
# Clear test log
rm -f /tmp/claude-hook-test.log
echo ""
echo "📁 Test environment created at: $TEST_DIR"
echo "📝 Hook script: $TEST_DIR/workspace/subdir/.claude/hooks/test-hook.sh"
echo "📝 Local settings: $TEST_DIR/workspace/subdir/.claude/settings.json"
echo "📝 Global settings: ~/.claude/settings.json"
echo ""
echo "✅ Manual hook test:"
echo '{"test": true}' | "$TEST_DIR/workspace/subdir/.claude/hooks/test-hook.sh" && echo " Script works when run manually ✓"
echo ""
echo "🚀 Next steps:"
echo "1. cd $TEST_DIR/workspace/subdir"
echo "2. claude --dangerously-skip-permissions"
echo "3. Send any message to Claude"
echo "4. Ask Claude to run: cat /tmp/claude-hook-test.log"
echo ""
echo "📊 Expected: Hook log entries appear"
echo "📊 Actual: No log file created (hooks never fire)"
echo ""
echo "Cleanup: rm -rf $TEST_DIR && mv ~/.claude/settings.json.backup ~/.claude/settings.json 2>/dev/null || true"
Reproduction Steps
From any subdirectory:
- Run the reproduction script:
``bash``
chmod +x claude-hooks-reproduction-test.sh
./claude-hooks-reproduction-test.sh
- Follow the instructions printed by the script:
``bash``
cd /tmp/claude-hooks-test-XXXXX/workspace/subdir
claude --dangerously-skip-permissions
- Send any message to Claude
- Ask Claude: "Check if the hook log exists:
cat /tmp/claude-hook-test.log"
Expected Result:
🎬 Hook executed! Sun Oct 26 08:30:15 WET 2025
Stop: 2025-10-26 08:30:16
Actual Result:
cat: /tmp/claude-hook-test.log: No such file or directory
---
Testing Evidence
✅ Manual Script Execution Works
$ echo '{"tool":"Bash"}' | /tmp/claude-hooks-test-12345/workspace/subdir/.claude/hooks/test-hook.sh
🎬 Hook fired!
$ cat /tmp/claude-hook-test.log
🎬 Hook executed! Sun Oct 26 08:30:15 WET 2025
❌ Claude Code Never Invokes Hooks
$ cat /tmp/claude-hook-test.log
cat: /tmp/claude-hook-test.log: No such file or directory
Despite 15+ restarts, /hooks command usage, and testing all hook types.
---
Workarounds Attempted
| Approach | Result |
|----------|--------|
| Absolute paths in hook commands | ❌ No execution |
| Symlink ~/.claude/settings.json to project .claude/ | ❌ No execution |
| Inline commands (no script files) | ❌ No execution |
| All hook types (SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop) | ❌ None execute |
| /hooks command after every change | ❌ No change |
| Complete Claude Code restarts (15+) | ❌ No change |
---
Expected vs. Actual Behavior
According to docs:
User settings are defined in ~/.claude/settings.json and apply to all projects.
Expected:
- Hooks in
~/.claude/settings.jsonexecute regardless ofcwd - Hooks in local
.claude/settings.jsonexecute when present /hookscommand reflects loaded hooks- Hooks fire after restart
Actual (in subdirectories):
- Global hooks (
~/.claude/settings.json) don't execute - Local hooks (
.claude/settings.json) don't execute /hookscommand doesn't show configured hooks- No hook process spawned, no logs, no errors
Actual (from ~ directory):
- Hooks reportedly work ~80-90% of the time (Issue #8810)
- Duplicate firing reported (Issue #3465)
---
Impact Assessment
Severity: 🔴 CRITICAL
Blocked Use Cases:
- CI/CD Pipelines: Can't automate testing, validation, deployment
- Multi-Agent Workflows: Can't orchestrate parallel sessions
- Context Injection: Can't add project-specific context
- Security Validation: Can't enforce command approval
- Progress Tracking: Can't auto-update documentation
- Quality Gates: Can't block unsafe operations
Workaround: None. Only option is always launching from ~ (impractical).
---
Related Issues
- #8810: UserPromptSubmit hooks not working in subdirectories
- #6305: PreToolUse/PostToolUse hooks not executing
- #3579: User settings hooks not loading
- #2814: Hooks system configuration issues
- #6403: PostToolUse hooks not executing despite stdin JSON
- #3465: Duplicate hook firing from home directory
---
Proposed Solutions
Option 1: Fix Subdirectory Support (Immediate)
- Load
~/.claude/settings.jsonregardless ofprocess.cwd() - Resolve hook paths relative to settings file location
- Add logging: "Loaded N hooks from ~/.claude/settings.json"
Option 2: Debug Path Resolution (Root Cause)
- Verify
~/.claude/settings.jsonis read whencwd != ~ - Check working directory for hook spawning
- Log hook loading failures
Option 3: Add /hooks debug Command
$ claude /hooks debug
✅ Global settings: ~/.claude/settings.json (5 hooks loaded)
✅ Local settings: .claude/settings.json (3 hooks loaded)
❌ Hook execution: 0 hooks fired in session
⚠️ Subdirectory issue: known bug #8810
Option 4: Provide Source Code Access
For community debugging:
- Settings loading logic
- Hook spawning mechanism
- Path resolution code
---
Request for Anthropic Team
- Acknowledge as critical bug affecting core feature
- Prioritize fix - hooks are advertised but broken
- Share debugging guidance - how can community help?
- Provide workaround - any way to make hooks work in subdirs?
- Update docs - clarify if "hooks only work from ~" is intended
---
Testing Offer
We're happy to:
- Test patches before release
- Provide detailed logging
- Contribute documentation improvements
- Share CI/CD workflow designs
---
Summary: Hooks are completely non-functional in subdirectories (v2.0.27), blocking CI/CD use cases. Combines bugs #8810 and #6305. Needs urgent fix.
This issue has 9 comments on GitHub. Read the full discussion on GitHub ↗