Hook execution failures are invisible in the desktop app and carry zero diagnostics (hook_non_blocking_error attachments with empty content)

Status Open
Reported on v2.1.111
Maintainer reply None cached
Activity 2 comments · opened Aug 22, 2026

Environment

macOS (Darwin 25.x), Claude Code desktop app (entrypoint: claude-desktop in transcripts), observed continuously across versions 2.1.111 through 2.1.237 (April to August 2026). Project registers ~42 hooks in .claude/settings.json with relative commands (.claude/hooks/foo.sh).

Background (mechanism is known; that is not this report)

We independently rediscovered what #50960 and #3583 describe: the session's persistent shell cwd drifts after a cd, relative hook commands resolve against the drifted cwd, and every hook spawn fails until the cwd resets. #50960 was closed as not planned, and $CLAUDE_PROJECT_DIR exists as the documented mitigation, so we are not re-filing the mechanism. This report is about observability: what the product shows (and stores) when hook execution fails.

Problems

1. In the desktop app, hook execution failures are completely invisible at run time. The hooks guide says a non-blocking hook error surfaces "a <hook name> hook error notice." In claude-desktop sessions we find no such notice anywhere in the UI or the model's context. The only artifact is a transcript attachment record:

{"type": "attachment", "attachment": {"type": "hook_non_blocking_error", "hookName": "PreToolUse:Edit", "content": ""}}

2. The failure record carries zero diagnostics. Across 72,538 such records in our transcript history (140 of 185 sessions), content is empty in every single one. No errno, no path, no exit status, no stderr. #50960's CLI report at least showed [Errno 2] No such file or directory; the desktop app's records preserve nothing. Diagnosis required reconstructing the failure from group-size arithmetic (failures arrive in exact matcher-group multiples: 14 for our Edit|Write|MultiEdit group, 9-10 Bash, 3 PostToolUse, 2 Read, 4 Stop) and correlating with the transcript's per-record cwd field.

3. Consequence: policy hooks silently do not exist, at scale, with no operator signal. For an org using PreToolUse hooks as guardrails (protected-file gates, content validation), each drift window means every guardrail is skipped and nothing tells anyone. We ran this way for four months before noticing, and only noticed because a gate's audit log went quiet. We have now shipped our own detector hook that scans the transcript for these attachment records and reports the backlog, which works, but it is a workaround for missing product observability.

Requests

  1. Populate content (or dedicated fields) on hook_non_blocking_error with the spawn/exec error: errno, resolved command path, working directory, exit status, stderr tail.
  2. Surface hook execution failures in the desktop app UI (the documented "hook error" notice does not appear there), or at minimum aggregate a per-session count somewhere visible.
  3. Consider resolving relative hook command paths against the project root by default (or warning at startup when hook commands are relative), since $CLAUDE_PROJECT_DIR is opt-in and the failure mode is silent-by-default.

Repro

  1. Project with a PreToolUse hook registered as a relative path (.claude/hooks/x.sh) in a claude-desktop session.
  2. Let a Bash tool call persist a cd into a subdirectory.
  3. Trigger the hooked tool. The hook does not run; no UI notice appears; the transcript gains hook_non_blocking_error attachments with empty content.

Happy to provide scrubbed transcript excerpts (attachment records only) and claude --debug logs on request.

View original on GitHub ↗

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