2.1.141: Notification:permission_prompt hook event stops firing during active thinking (regression from 2.1.139)

Resolved 💬 6 comments Opened May 14, 2026 by ww2283 Closed May 17, 2026

Summary

In 2.1.141, the Notification hook event with notification_type: permission_prompt does not fire when a permission prompt appears while the assistant is in an active thinking phase. The prompt renders correctly in the TUI; downstream hooks subscribed to Notification never run.

Confirmed working in 2.1.139 with the same project, settings, and hook scripts. 2.1.140 not tested.

Repro

  1. Configure a Notification hook with matcher permission_prompt:

``jsonc
"Notification": [{
"matcher": "permission_prompt",
"hooks": [{"type": "command", "command": "/tmp/log-prompt.sh"}]
}]
`
where
log-prompt.sh is echo "$(date '+%H:%M:%S') fired" >> /tmp/prompts.log`.

  1. Run any task in extended-thinking mode that triggers a permission prompt mid-reasoning. The unsandboxed-Bash override prompt is reliable because it always appears mid-thinking.
  2. Observe: prompt renders, user sees it. /tmp/prompts.log gets no new line.
  3. Downgrade to 2.1.139, repeat. /tmp/prompts.log gets a new line for every prompt.

PreToolUse and AskUserQuestion hooks fire correctly on 2.1.141 — only Notification:permission_prompt is affected.

Suspected mechanism (from the 2.1.141 binary)

The permission_prompt Notification trigger is gated by a 6-second user-idle check:

const NIK = 6000;

function AR()  { return m_.lastInteractionTime; }
function hF3() { return Date.now() - AR(); }
function EF3(H){ return hF3() < H; }
function SF3(H){ return !EF3(H); }            // idle ≥ H ms

function KG_(message, notificationType) {     // permission_prompt path
  ...
  P1(() => {
    if (SF3(NIK)) {
      setFired(true);
      MHH({ message, notificationType }, q);   // ← actual hook fire
    }
  }, fired ? null : NIK);
}

m_.lastInteractionTime is reset by bt8() (called via u6H(true)Ci8()). Ci8() is invoked from the SDK control-request handlers:

if (plK(K.request)) Ci8(...)   // adjacent to can_use_tool / pendingRequests
if (mlK(_))         Ci8(...)   // adjacent to onControlRequestResolved

The release note for 2.1.141 mentions the spinner now warms to amber after 10 seconds during long thinking periods. That feature appears to emit periodic progress/heartbeat control messages on the same channel — each tick matches plK/mlK, calls Ci8 → bt8 → lastInteractionTime = Date.now(). The 6-second idle window never accumulates during thinking, so SF3(6000) stays false and MHH(...) is never reached.

Symbol names may roll in subsequent versions; the structural pattern is KG_(..., "permission_prompt"), gated by SF3(NIK) with NIK = 6000, with lastInteractionTime reset via Ci8() from control-request predicates.

Impact

Any hook subscribed to Notification:permission_prompt silently misses prompts that arrive mid-thinking. The terminalSequence hook output field added in this same release is also effectively unreachable for permission_prompt, since it depends on the parent event firing.

Suggested fix

Exclude progress/spinner heartbeats from the user-interaction reset. Options:

  • Narrow plK/mlK predicates so spinner heartbeats don't match.
  • Tag heartbeat control messages and skip Ci8() for them.
  • Decouple the permission_prompt Notification gate from lastInteractionTime — user-input idle is the wrong proxy when the assistant is what's keeping the system "active".

Environment

  • claude-code 2.1.141 (Mach-O arm64, macOS 25.5.0)
  • Verified working: 2.1.139
  • Not tested: 2.1.140
  • Terminal: VS Code Insiders integrated terminal + tmux; also reproduces in Ghostty

View original on GitHub ↗

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