Skip to content

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

Description

@ww2283

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:
    "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.
  2. 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.
  3. Observe: prompt renders, user sees it. /tmp/prompts.log gets no new line.
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions