From d80fc1808a133de02ca1f43773ef688a4464326d Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Wed, 5 Aug 2026 15:12:47 -0700 Subject: [PATCH] fix(hooks): replace run-hook.cmd heredoc with :; label lines (#571) bash >= 5.1 writes heredocs into a pipe pre-fork; under macOS pipe pressure the kernel hands out 512-byte pipes and the wrapper's 1.4KB CMDBLOCK heredoc deadlocks every hook. Label lines parse nowhere: cmd.exe skips them as labels, POSIX shells exec away before the batch block. Also: silent fail-open when bash or the script name is missing, and CDPATH-proof directory resolution. --- hooks/run-hook.cmd | 26 +++++++++++++++----------- tests/hooks/test-session-start.sh | 9 +++++++++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/hooks/run-hook.cmd b/hooks/run-hook.cmd index ceec3a73..5c89f05b 100755 --- a/hooks/run-hook.cmd +++ b/hooks/run-hook.cmd @@ -1,8 +1,20 @@ -: << 'CMDBLOCK' +:; command -v bash >/dev/null 2>&1 || exit 0 +:; [ $# -ge 1 ] || exit 0 +:; SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +:; SCRIPT_NAME="$1"; shift; exec bash "${SCRIPT_DIR}/${SCRIPT_NAME}" "$@" @echo off REM Cross-platform polyglot wrapper for hook scripts. -REM On Windows: cmd.exe runs the batch portion, which finds and calls bash. -REM On Unix: the shell interprets this as a script (: is a no-op in bash). +REM On Unix: POSIX shells execute the ":;" lines above and exec away before +REM reaching this batch block (":" is a no-op; cmd.exe treats lines starting +REM with ":" as labels and skips them). If bash or the script name is +REM missing, the wrapper exits 0 silently - hooks are optional context, never +REM a session breaker. +REM On Windows: cmd.exe runs this batch portion, which finds and calls bash. +REM +REM Heredocs are banned in this file and every hooks/ executable: bash 5.1+ +REM delivers them via a pre-fork pipe write that deadlocks on macOS under +REM pipe pressure (issue #571). tests/hooks/test-no-heredocs-in-hooks.sh is +REM the fence. REM REM Hook scripts use extensionless filenames (e.g. "session-start" not REM "session-start.sh") so Claude Code's Windows auto-detection -- which @@ -35,12 +47,4 @@ if %ERRORLEVEL% equ 0 ( ) REM No bash found - exit silently rather than error -REM (plugin still works, just without SessionStart context injection) exit /b 0 -CMDBLOCK - -# Unix: run the named script directly -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -SCRIPT_NAME="$1" -shift -exec bash "${SCRIPT_DIR}/${SCRIPT_NAME}" "$@" diff --git a/tests/hooks/test-session-start.sh b/tests/hooks/test-session-start.sh index c1508290..2ca925ee 100755 --- a/tests/hooks/test-session-start.sh +++ b/tests/hooks/test-session-start.sh @@ -184,6 +184,15 @@ assert_command_output \ CLAUDE_PLUGIN_ROOT="$REPO_ROOT" \ bash "$WRAPPER_UNDER_TEST" session-start +# With no bash available, the wrapper must fail open: silent, exit 0. +if output="$(env -i PATH=/nonexistent /bin/sh "$WRAPPER_UNDER_TEST" session-start 2>&1)" \ + && [ -z "$output" ]; then + pass "wrapper with no bash on PATH is silent and exits 0" +else + fail "wrapper with no bash on PATH is silent and exits 0" + printf '%s\n' "$output" | head -3 | sed 's/^/ /' +fi + cursor_home="$(make_home cursor)" assert_command_output \ "Cursor emits top-level additional_context only" \