[BUG] Stop hook fires after every response, not just at task completion

Resolved 💬 4 comments Opened Dec 24, 2025 by coygeek Closed Feb 14, 2026

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:

  1. Claude runs git commitStop fires (false positive - task not complete)
  2. Claude runs git pushStop 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

  1. 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"'
])
  1. Configure in settings.json:
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 /path/to/stop_notify.py"
          }
        ]
      }
    ]
  }
}
  1. Ask Claude: "commit my changes and push to origin"
  1. 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

  1. Add stop_reason field: Include why Claude stopped (tool_use, end_turn, max_tokens)
  2. Add is_final boolean: Simple flag indicating no more work pending
  3. New hook type: Create a separate TaskComplete hook 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

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗