Ver Fonte

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.
Drew Ritter há 1 mês atrás
pai
commit
d80fc1808a
2 ficheiros alterados com 24 adições e 11 exclusões
  1. 15 11
      hooks/run-hook.cmd
  2. 9 0
      tests/hooks/test-session-start.sh

+ 15 - 11
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}" "$@"

+ 9 - 0
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" \