[BUG] Stop hook fires after every response, not just at task completion
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The Stop hook fires after every Claude response, including intermediate responses during multi-step workflows. This makes it impossible to use Stop hooks for "task complete" notifications without false positives.
For example, when Claude executes git commit && git push:
- Claude runs
git commit→ Stop fires (false positive - task not complete) - Claude runs
git push→ Stop fires (correct - task complete)
The user receives two "Task complete" notifications when only one was expected.
What Should Happen?
Either:
Option A: Stop hook should only fire when Claude has truly completed the user's request (no more tool calls pending)
Option B: Stop hook input should include a field indicating whether this is a mid-task pause or actual completion, e.g.:
{
"session_id": "abc123",
"stop_reason": "tool_use" | "end_turn" | "max_tokens",
"has_pending_work": true | false
}
This would allow hook authors to filter appropriately.
Error Messages/Logs
From my notification hook logs during a "commit and push" workflow (actual timestamps from reproduction):
00:16:54 [INFO] [git_commit_marker] git_commit_detected: markers_created
00:17:37 [INFO] [notify] starting <- STOP FIRES (false positive - after commit)
00:17:47 [INFO] git push origin HEAD
00:17:50 [INFO] [clear_git_marker] markers_cleared
00:17:55 [INFO] [notify] starting <- STOP FIRES (correct - after push)
**2 Stop events** for a single user request ("commit and push"). The first one at 00:17:37 is a false positive - Claude has more work to do (the push).
Steps to Reproduce
- Create a Stop hook that logs or notifies:
#!/usr/bin/env python3
import json
import sys
import subprocess
data = json.load(sys.stdin)
subprocess.run([
"osascript", "-e",
'display notification "Task complete" with title "Claude"'
])
- Configure in
settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python3 /path/to/stop_notify.py"
}
]
}
]
}
}
- Ask Claude: "commit my changes and push to origin"
- Observe: Multiple "Task complete" notifications appear (one after each tool execution)
Claude Model
None
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.0.76
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
My Workaround
I implemented a marker-based suppression system using cross-hook state:
| Hook | Trigger | Action |
|------|---------|--------|
| git_commit_marker.py | PreToolUse: detects git commit | Creates marker file in /tmp |
| notify.py | Stop event | Checks for marker, suppresses if present |
| clear_git_marker.py | PostToolUse: detects git push | Deletes marker file |
This works but requires:
- Knowledge of specific command patterns to suppress
- Complex cross-hook coordination
- Marker file management with expiry logic
This workaround should not be necessary. The Stop hook should provide enough context to distinguish mid-task pauses from actual completion.
Proposed Solutions
- Add
stop_reasonfield: Include why Claude stopped (tool_use,end_turn,max_tokens) - Add
is_finalboolean: Simple flag indicating no more work pending - New hook type: Create a separate
TaskCompletehook that only fires at actual completion
Use Case
Notification hooks are a natural fit for the Stop event - alerting users when Claude needs attention or has finished. But the current behavior makes this unreliable for the "finished" case without elaborate workarounds.
Environment
- Claude Code version: latest (2024-12-24)
- OS: macOS 15.2
- Shell: zsh
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗