Hooks: /bin/sh parses command before bash executes, breaking bash-specific syntax (<<<, process substitution, etc.)

Resolved 💬 3 comments Opened Apr 2, 2026 by tianhaocui Closed Apr 5, 2026

Bug Description

When a hook's command field uses bash as the executable but includes bash-specific syntax like herestrings (<<<), the command fails with Syntax error: redirection unexpected because Claude Code uses /bin/sh to parse and execute the entire command string before bash gets invoked.

Reproduction

settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/my-hook.sh tool_start <<< '{\"tool_name\":\"'\"$TOOL_NAME\"'\"}'",
            "timeout": 5000
          }
        ]
      }
    ]
  }
}

Expected: bash is invoked and handles the <<< herestring natively.

Actual: /bin/sh parses the command first, encounters <<<, and fails:

Syntax error: redirection unexpected

This happens on every tool call, making hooks unusable when bash-specific syntax is present.

Root Cause

Claude Code appears to execute hook commands via /bin/sh -c "<command>" (or equivalent). Since /bin/sh is POSIX sh on many systems (e.g., dash on Debian/Ubuntu), any bash-specific syntax in the command string is rejected at the shell parsing stage — before bash ever runs.

This affects at minimum:

  • Herestrings: <<<
  • Process substitution: <(), >()
  • Arrays, [[ ]], and other bashisms

Suggested Fix

A few options:

  1. Use bash -c instead of sh -c to execute hook commands (may have portability concerns).
  2. Document the limitation prominently — hook commands must use POSIX sh-compatible syntax only.
  3. Allow users to specify the shell for hook execution, e.g., a "shell" field in the hook config.

Workaround

Rewrite commands to be POSIX-compatible. For example, replace:

"command": "bash ~/hook.sh tool_start <<< '{\"tool_name\":\"'\"$TOOL_NAME\"'\"}'"

With:

"command": "echo '{\"tool_name\":\"'\"$TOOL_NAME\"'\"}' | bash ~/hook.sh tool_start"

Or if the script doesn't actually read stdin (as was the case here), simply remove the herestring entirely.

Environment

  • OS: Ubuntu (remote server), /bin/shdash
  • Claude Code version: latest

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗