Skip to content

feat(hooks): attach non-blocking hook failures to the tool result - #245

Merged
lzvxck merged 6 commits into
devfrom
cursor/hook-failure-notices-7223
Oct 5, 2026
Merged

lzvxck merged 6 commits into
devfrom
cursor/hook-failure-notices-7223

Conversation

@lzvxck

@lzvxck lzvxck commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #241

What and why

A PreToolUse hook that exits 1 currently deny-blocks the call, and a PostToolUse failure is a TUI error event only. The model never sees the failure, so it cannot fix a broken hook and hits the same silent miss on every call.

Non-blocking hook failures (non-zero other than exit 2, timeout, crash after start) now attach a short, session-deduplicated notice to that tool call's result: hook name, exit code, and a stderr excerpt capped at HOOK_REASON_MAX_CHARS (300, tail). Identical repeats collapse. Spawn and a missing or non-regular script file still deny (unrunnable), which is #173, not this issue.

How

  • HookOutcome splits failed (hook started, non-blocking) from unrunnable (never started, still deny).
  • createHookRunner reports failed notices without blocking, collapses identical messages for the session, and still fail-closes spawn. A directory or unreadable path is unrunnable before spawn (bash would exec a directory and exit 126).
  • runLoop records every tool-phase row through recordToolResult. A json output with notices becomes text whose prefix is JSON.stringify(value) (the same bytes the clean path puts on the wire) plus a Hook notices: block. Leftover PreToolUse notices also ride along when a later hook or the permission gate denies the call.

Rejected: wrapping the result in a new output type, a missing-script stderr heuristic (#236), and treating bash exit 127 as "never ran".

Verification

  • bun run typecheck
  • bun test apps/cli/tests/hooks/run.test.ts apps/cli/tests/hooks/gate.test.ts apps/cli/tests/loop/loop.tools.test.ts apps/cli/tests/loop/parallelReadEvents.test.ts apps/cli/tests/loop/loop.pathDenial.test.ts apps/cli/tests/imageParts.test.ts (148 pass, 5 skip)
  • bun run build, and ran the resulting binary (seri 0.1.3)
  • Exercised the change by hand (say how, below)

Negative control: PreToolUse exit 1 used to set block; the new test expects the tool to run and the tool-result to contain the stderr once, then collapse the repeat. A later hook block or a read-only gate deny still carries the leftover notice.

Cross-platform impact

  • Touches file paths, file I/O, process spawning, signals, or shell invocation
  • If ticked: verified on both a POSIX shell and PowerShell

Linux live-bash hook tests pass. PowerShell live tests are describe.skip here. CI covers Windows.

Windows build on an earlier SHA died after every test passed: Bun 1.4.0 segfault at process teardown. That is the same class Ubuntu already splits the suite for. This branch runs that split on Windows too.

Blast Radius

Hook runner plus the loop's tool-result rows. Path denials still skip PreToolUse. Containment denials are unchanged. Subagents share the same runner, so collapse is drive-wide: a child never sees a notice the parent already delivered, and a compacted row does not re-arm it. Parallel reads still attach PreToolUse notices without painting a false in-flight throw. A dropped image next to a notice text part still keeps the "dropped image" placeholder.

Notes for the reviewer

Open in Web Open in Cursor 

cursoragent and others added 6 commits October 5, 2026 06:42
A PreToolUse hook that exits 1, times out, or crashes after it starts lets the tool run. createHookRunner attaches one copy of that notice to the tool result for the session. A hook that never starts still denies the call.

Co-authored-by: Lionel <[email protected]>
An object result becomes a json string with the notice suffix. An image result keeps its file part and gains a text part.

Co-authored-by: Lionel <[email protected]>
Cap the stderr excerpt, not the whole `script exited N:` line, so the
model still sees which hook failed. Attach leftover PreToolUse notices
when a later hook or the permission gate denies the call.

Co-authored-by: Lionel <[email protected]>
Bun 1.4.0 segfaults after a long single-process `bun test` on Windows
even when every case passed. Ubuntu already splits by directory for
that crash; run the same steps on Windows.

Co-authored-by: Lionel <[email protected]>
Providers JSON.stringify json outputs, so a string suffix on an object
result double-encoded write_file and bash. A json row with notices is
now text whose prefix matches the clean path. recordToolResult is the
only tool-phase push. Directories fail closed before spawn.

Co-authored-by: Lionel <[email protected]>
Ubuntu and Windows passed on 7e49995. macOS failed
transcriptSelection.test.tsx with shown="" for entry 17, which this
branch does not touch.

Co-authored-by: Lionel <[email protected]>
@lzvxck
lzvxck merged commit 7509c14 into dev Oct 5, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants