Repository navigation
hooks: the node-notice row exits 127 on hosts where /bin/sh is not bash, one hook error per plugin at every session start (25 plugins) #6130
Description
Summary
The node-notice row that 25 plugins carry on main exits 127 on a Linux host whose /bin/sh is dash. The row ends the POSIX half with ${BASH_VERSION:+exit}, which only exits under bash. Under dash BASH_VERSION is unset, the expansion is empty, and the PowerShell half runs: powershell: not found. Every session start then records one non-blocking hook error per plugin carrying the row.
Affected area / component
hooks/hooks.json SessionStart row in 25 plugins (list below); plugins/testing/scripts/gen-hook-filters.sh:56 generates the testing copy.
Steps to reproduce
-
On a host where
readlink -f /bin/shprints/usr/bin/dash, start a session with any plugin carrying the row. The row, for exampleplugins/context-guard/hooks/hooks.jsononmain(origin/mainf605a59639e4):sh "${CLAUDE_PLUGIN_ROOT}/lib/prerequisites.sh" node-notice /context-guard:check CONTEXT_GUARD_HOOKS_ENABLED; ${BASH_VERSION:+exit}; powershell -NoProfile -ExecutionPolicy Bypass -File "${CLAUDE_PLUGIN_ROOT}/lib/prerequisites.ps1" node-notice /context-guard:check CONTEXT_GUARD_HOOKS_ENABLED -
Read the session's
--debuglog or transcript forSessionStart:startup. -
Minimal check of the guard alone: a file holding
true; ${BASH_VERSION:+exit}; echo reached, run assh <file>on that host, printsreached(the expansion did not exit). Run from a file because the inlinesh -c '...'form was refused by a local tool guard; the text is the same.
Plugins carrying the row on main (git grep -l -F 'BASH_VERSION:+exit' origin/main -- 'plugins/*/hooks/hooks.json'): actionlint, animation, autonomy, bash-format, biome-format, context-budget, context-guard, desktop-notification, disk-hygiene, eol-normalizer, explainer-video, go-format, guardrails, harness-ops, instruction-placement, markdown-format, multi-agent, powershell-format, rate-limit-guard, ruff-format, session-flow, source-control, speech, testing, typos-format.
Expected behavior
On a POSIX host the row runs the .sh stub and stops; no hook error is recorded. The convention says so: docs/conventions/prerequisites/README.md:145, "bash takes the first and leaves at ${BASH_VERSION:+exit}; PowerShell ... has no sh, skips to the second."
Actual behavior
Claude Code runs a shell-form hook with sh -c on Linux and macOS (https://code.claude.com/docs/en/hooks, shell form, fetched 2026-10-03). On this host /bin/sh is dash, so the guard never fires. The convention document does not mention dash or exit 127 anywhere, so this is not a documented or accepted outcome.
runs/a7-node-present.debug.log:201: [ERROR] "Hook SessionStart:startup (SessionStart) error: status code 127\nstderr:\n/bin/sh: 1: powershell: not found"
runs/a27.debug.log:536: [ERROR] "Hook SessionStart:startup (SessionStart) error: status code 127\nstderr:\n/bin/sh: 1: powershell: not found"
runs/a27.debug.log:538, 540, ... 576: the same line, 21 in all (every second line)
runs/a7-node-present.transcript.jsonl: {"type":"hook_non_blocking_error","hookName":"SessionStart:startup","hookEvent":"SessionStart","stderr":"Failed with non-blocking status code: /bin/sh: 1: powershell: not found","stdout":"","exitCode":127,...}
runs/a7-node-absent.debug.log:212: [ERROR] "Hook SessionStart:startup (SessionStart) error: status code 127\nstdout:\n{\"systemMessage\":\"rate-limit-guard: node is not on PATH, so the hooks of this plugin ... cannot start and do nothing. ...\",\"hookSpecificOutput\":{\"hookEventName\":\"SessionStart\",\"additionalContext\":\"WARNING: rate-limit-guard: node is not on PATH ...
runs/a27.debug.log:1248: harness-ops: 21 hook failure record(s) in this session's transcript were never surfaced: SessionStart:startup [Checking that node is on PATH...] (21x; ambiguous: exit 126/127 with no exec-failure signature; ...
(Log lines come from a local mods probe, live-b/, not committed. The a27 run loaded 21 plugins carrying the row; a7-* loaded one.)
Impact observed:
- One 127 record per plugin carrying the row, at every session start, with node present and with node absent.
- With node absent the notice still reached Claude: the stub's
systemMessageandadditionalContextare in the 127 record (a7-node-absent.debug.log:212) and the model repeated the warning in its reply. - The
harness-opsfailure report counts these 127 records as hook failures.
Not probed: a host where /bin/sh is bash, macOS, and Windows with and without Git Bash.
Direction (not prescriptive, untested)
Decide the branch on something dash can test, such as whether powershell resolves, instead of on BASH_VERSION; or move the row to the exec form discussed in #3686 (closed). Any change has to keep the Windows-without-Git-Bash path working and be applied to the generator at plugins/testing/scripts/gen-hook-filters.sh:56 as well as the 25 hooks.json files.
Environment
Claude Code 2.1.288, Linux (WSL2) with /bin/sh -> /usr/bin/dash, probe on 2026-10-03.
Activity
kyle-sexton commentedon Oct 4, 2026
This was generated by AI during triage.
Close #6130 as already implemented? Recommended: yes
Triage
${PPID:+exit}so dash skips the PowerShell half". Its body says "No related issue", so it did not auto-close this one.BASH_VERSION:+exitinplugins/*/hooks/hooks.jsonreturns no file, and${PPID:+exit}appears in 26 files (the 25 hook plugins'hooks.jsonplus thetestinggeneratorgen-hook-filters.sh). Example:plugins/context-guard/hooks/hooks.json:9. fix(prerequisites): stop the node-notice row at ${PPID:+exit} so dash skips the PowerShell half #6131 also updateddocs/conventions/prerequisites/README.mdand the canonical-row test.powershell: not founderrors on WSL/Ubuntu as the symptom it fixed.Why human-gated: this lane does not close items. A maintainer confirms the close, or reopens the question if a dash host still shows exit 127 after updating to a plugin version that carries #6131.
kyle-sexton commentedon Oct 11, 2026
Fixed by merged PR #6131 (
\). Verified on main 2a5f9b0:git grep -F 'BASH_VERSION:+exit' origin/main -- 'plugins/*/hooks/hooks.json'returns nothing. Closing with the owner's approval.