Skip to content

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

Closed

Description

@kyle-sexton

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

  1. On a host where readlink -f /bin/sh prints /usr/bin/dash, start a session with any plugin carrying the row. The row, for example plugins/context-guard/hooks/hooks.json on main (origin/main f605a59639e4):

    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
    
  2. Read the session's --debug log or transcript for SessionStart:startup.

  3. Minimal check of the guard alone: a file holding true; ${BASH_VERSION:+exit}; echo reached, run as sh <file> on that host, prints reached (the expansion did not exit). Run from a file because the inline sh -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 systemMessage and additionalContext are in the 127 record (a7-node-absent.debug.log:212) and the model repeated the warning in its reply.
  • The harness-ops failure 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

  1. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 3, 2026
  2. added theissue type on Oct 3, 2026
  3. kyle-sexton commented on Oct 4, 2026

    @kyle-sexton
    ContributorAuthor

    This was generated by AI during triage.

    Close #6130 as already implemented? Recommended: yes

    Triage

    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.

  4. added
    priority: lowNice-to-have, cosmetic, or speculative; opportunistic.
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    status: needs-decisionAwaiting a human or maintainer judgment call.
    and removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 4, 2026
  5. kyle-sexton commented on Oct 11, 2026

    @kyle-sexton
    ContributorAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.priority: lowNice-to-have, cosmetic, or speculative; opportunistic.status: needs-decisionAwaiting a human or maintainer judgment call.

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions