[FEATURE] User Interrupt Hook

Open 💬 25 comments Opened Oct 14, 2025 by arhgap11b

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

Summary

Add a new hook event that fires when the user manually interrupts Claude Code's response (e.g., by clicking Stop or pressing Ctrl+C).

Problem

Currently, Claude Code provides a Stop hook that fires when the agent finishes responding normally. However, according to the documentation:

"Does not run if the stoppage occurred due to a user interrupt."

This creates a gap: there is no way to detect when the user manually interrupts the agent. This prevents implementing features that need to differentiate between:

  • Normal completion (agent finished its work)
  • User interrupt (user manually stopped the agent)

Proposed Solution

Add a new hook event: UserInterrupt (or AgentInterrupted, Stop:Interrupted, etc.)

This hook should fire when:

  • User clicks the Stop button during agent response
  • User presses keyboard interrupt (Ctrl+C or equivalent)
  • User manually cancels agent operation through any UI mechanism

Example Configuration

{
  "hooks": {
    "Stop": [{
      "hooks": [{
        "type": "command",
        "command": "my-script.py --event normal_completion"
      }]
    }],
    "UserInterrupt": [{
      "hooks": [{
        "type": "command",
        "command": "my-script.py --event user_interrupted"
      }]
    }]
  }
}

Alternative Solutions

_No response_

Priority

Medium - Would be very helpful

Feature Category

CLI commands and flags

Use Case Example

We're implementing an audio notification system that plays background noise while Claude works and plays a completion sound when finished. We need to:

  • Play a "work complete" sound when the agent finishes normally (Stop hook)
  • Play a different "interrupted" sound when the user manually stops the agent
  • Currently impossible because there's no hook for user interrupts

Additional Context

Benefits

  1. Feature parity: Completes the lifecycle coverage (start → working → end/interrupt)
  2. Better UX: Enables context-aware notifications and state management
  3. Debugging: Helps track user interaction patterns and interruption frequency
  4. Resource cleanup: Allows proper cleanup of resources when operations are interrupted vs. completed

Workarounds (and why they don't work)

  1. Timeout-based detection: Unreliable, creates false positives for long-running tasks
  2. Heartbeat with PreToolUse: Fires on every tool call, creates noise and complexity
  3. Process monitoring: External to Claude Code, fragile and platform-specific

Related Documentation

View original on GitHub ↗

25 Comments

arhgap11b · 9 months ago

Would be great if we could have hook on context limit (when auto-compact turned off)!

github-actions[bot] · 7 months ago

This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.

mpercy · 6 months ago

Would be nice to see this fixed for edge-triggered status / notification purposes

CharlonTank · 6 months ago

I need that too!

jeffc-dev · 6 months ago

Consider me another vote for this.

I'm working on a utility that watches Claude activity and is effectively 'stuck' if it can't detect manual interuption.

bernarduh · 5 months ago

I could use the Interrupted hook too 🙏

maxisme · 5 months ago

Very keen for this feature it is leaving my Claude code wrapper in very strange states.

alvarolb · 5 months ago

I need this one too ;)

johnrobinsn · 5 months ago

Pretty please 🙏 Thanks!

mwurm293 · 5 months ago

please

XciD · 5 months ago

Would love to see this one too

pbjer · 4 months ago

Also shopping for this.

jurab · 4 months ago

+1

Jukebox7 · 4 months ago

Do not interrupt this issue !
I need it to ! ✨

PabloLION · 4 months ago

Confirming this affects session status monitoring tools as well.

Use case: Agent Console Dashboard (ACD) — a TUI that tracks Claude Code session status by listening to hook events. When a session is active, ACD sets status to working. It relies on the Stop hook to transition the status back to attention (idle). Because Stop does not fire on user interrupt, any session where the user presses ESC or Ctrl+C is permanently stuck showing working until the next hook event fires.

Why workarounds fail for this case:

  • Timeout: a session that is interrupted mid-task looks identical to one that is still running a long tool call
  • PreToolUse heartbeat: only fires during tool execution, not during the initial inference phase when interrupts most commonly happen
  • Process monitoring: fragile across macOS/Linux, breaks with remote sessions

Environment:

| Component | Version |
|-----------|---------|
| Claude Code | latest |
| Platform | macOS 15.x (Darwin) |

The Stop hook gap is documented in the official hook reference ("Does not run if the stoppage occurred due to a user interrupt") but there is no alternative hook that covers this case. A UserInterrupt or Stop:interrupted hook event would close this gap cleanly.

yurukusa · 3 months ago

There's no native UserInterrupt hook yet, but you can approximate interrupt detection with existing hooks:

INPUT=$(cat)
REASON=$(echo "$INPUT" | jq -r '.stop_reason // "unknown"' 2>/dev/null)
HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // ""' 2>/dev/null)
MARKER="$HOME/.claude/.session-alive"
rm -f "$MARKER" 2>/dev/null
echo "$(date -Iseconds) clean-exit reason=$REASON" >> "$HOME/.claude/session-log.txt"
exit 0
MARKER="$HOME/.claude/.session-alive"
if [ -f "$MARKER" ]; then
    LAST=$(cat "$MARKER")
    echo "Previous session was interrupted (started: $LAST)" >&2
    echo "$(date -Iseconds) interrupted previous=$LAST" >> "$HOME/.claude/session-log.txt"
fi
echo "$(date -Iseconds)" > "$MARKER"
exit 0
date +%s > "$HOME/.claude/.heartbeat"
exit 0

External watcher (run in a separate terminal):

while true; do
    if [ -f "$HOME/.claude/.heartbeat" ]; then
        LAST=$(cat "$HOME/.claude/.heartbeat")
        NOW=$(date +%s)
        DIFF=$((NOW - LAST))
        if [ "$DIFF" -gt 30 ]; then
            echo "Claude interrupted or idle for ${DIFF}s"
        fi
    fi
    sleep 5
done
{
  "hooks": {
    "Notification": [{"matcher": "start", "hooks": [{"type": "command", "command": "bash ~/.claude/hooks/session-start-check.sh"}]}],
    "PostToolUse": [{"hooks": [{"type": "command", "command": "bash ~/.claude/hooks/heartbeat.sh"}]}],
    "Stop": [{"hooks": [{"type": "command", "command": "bash ~/.claude/hooks/interrupt-detector.sh"}]}]
  }
}

Not a perfect substitute for a native UserInterrupt event, but these patterns cover the main use cases: recovery after interrupt, logging interrupt vs clean exit, and real-time stall detection.

mieubrisse · 2 months ago

Very curious why this hasn't been implemented yet. It seems trivial to implement, and its absence plays havoc with tracking internal Claude state.

nathan-folsom · 1 month ago

Throwing in another request for this. Seems like a pretty major hole in Claude Code hooks coverage, and an annoyance for multi-agent workflows

juning98 · 1 month ago

+1 — this gap affects out-of-process integrations badly.

I maintain a local sidecar service that mirrors Claude Code's session state
to a hardware status indicator (LED). Without a hook for user interrupts,
the indicator gets stuck in "processing" forever after the user presses Esc
— there's no event to clear it until the next UserPromptSubmit, which may
never come if the user walks away.

Inferring interrupts by tracking unpaired PreToolUse / PostToolUse
works for the "interrupt then continue" case but completely misses
"interrupt then idle/close window", and forces every integrator to
reimplement the same pairing logic.

A dedicated UserInterrupt (or Interrupted) hook event with session_id,
cwd, and the cancelled tool_use_id(s) would solve this cleanly without
overloading Stop's existing semantics. Would love to see this prioritized.

NagyGeri69 · 1 month ago

I need this feature added asap.
As Claude Code doesn't support any other file encoding other than UTF-8, I'm using hooks on Read/Edit/Write to convert my files to UTF-8, then parse the edited file back to my encoding. However, if I don't accept a change, I can't detect that the file needs to be converted back.

FedeCuci · 1 month ago

+1

alfred-zhong · 22 days ago

+1

magic-sketch · 20 days ago

+1

ro0mquy · 8 days ago

+1

zhenghan3852 · 8 days ago

+1