claude-code - 💡(How to fix) Fix [BUG] PostToolUse hook on Skill matcher does not receive tool_input payload on Windows + Git Bash

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

Error Messages/Logs

Fix Action

Fix / Workaround

Workaround applied (downstream repo)

Code Example

{
    "tool_name": "Skill",
    "tool_input": { "skill": "<skill-name>", "args": "..." }
  }

---

#!/usr/bin/env bash
     # .claude/hooks/debug-capture.sh
     {
       echo "=== $(date) ==="
       echo "STDIN:"
       cat /dev/stdin || true
       echo "ARGS:"
       printf '  %s\n' "$@"
       echo "ENV (CLAUDE_CODE_*):"
       env | grep ^CLAUDE_CODE_ || true
       echo
     } >> /tmp/claude-hook-capture.log 2>&1

---

{
       "hooks": {
         "PostToolUse": [
           {
             "matcher": "Skill",
             "hooks": [
               { "type": "command", "command": "bash .claude/hooks/debug-capture.sh" }
             ]
           }
         ]
       }
     }

---

{
    "tool_name": "Skill",
    "tool_input": { "skill": "<skill-name>", "args": "..." }
  }

---



---

#!/usr/bin/env bash
     # .claude/hooks/debug-capture.sh
     {
       echo "=== $(date) ==="
       echo "STDIN:"
       cat /dev/stdin || true
       echo "ARGS:"
       printf '  %s\n' "$@"
       echo "ENV (CLAUDE_CODE_*):"
       env | grep ^CLAUDE_CODE_ || true
       echo
     } >> /tmp/claude-hook-capture.log 2>&1

---

{
       "hooks": {
         "PostToolUse": [
           {
             "matcher": "Skill",
             "hooks": [
               { "type": "command", "command": "bash .claude/hooks/debug-capture.sh" }
             ]
           }
         ]
       }
     }
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

Environment

  • OS: Windows 11 Pro (10.0.26200)
  • Shell for hooks: Git Bash (bash 5.x via Git for Windows)
  • Claude Code: invoked from VS Code on Windows; model claude-opus-4-7[1m]
  • Hook channel: PostToolUse matcher Skill, command bash .claude/hooks/telemetry-hook.sh
  • Settings file: .claude/settings.json (project scope)

Expected behavior (per the official Claude Code hook contract)

The hook's stdin should receive a JSON object describing the tool call that just completed. For the Skill matcher, the document should include at least:

{
  "tool_name": "Skill",
  "tool_input": { "skill": "<skill-name>", "args": "..." }
}

The hook's job is to read stdin, parse with jq, and act on .tool_name + .tool_input.skill.

Observed behavior

When a slash command body inlines a skill invocation, the hook fires but receives no payload. After instrumenting the hook with debug logging that captures stdin, all positional arguments, and all environment variables to a sidecar file on every fire:

  • A sidecar file is created on every invocation (so the hook IS being executed by the runtime).
  • stdin is empty.
  • Positional $@ is empty.
  • Only generic CLAUDE_CODE_PROJECT_DIR and CLAUDE_CODE_HOOK_NAME env vars are present.
  • No tool_name, no tool_input.skill, no JSON anywhere in the process inputs.

The downstream script (which receives the skill name as a positional argument) works correctly when invoked directly from the shell, so the failure is upstream in the hook delivery, not in the script logic.

Reproduction (minimal)

  1. Create a tiny capture hook:

    #!/usr/bin/env bash
    # .claude/hooks/debug-capture.sh
    {
      echo "=== $(date) ==="
      echo "STDIN:"
      cat /dev/stdin || true
      echo "ARGS:"
      printf '  %s\n' "$@"
      echo "ENV (CLAUDE_CODE_*):"
      env | grep ^CLAUDE_CODE_ || true
      echo
    } >> /tmp/claude-hook-capture.log 2>&1
  2. Wire it as a PostToolUse hook on the Skill matcher in .claude/settings.json:

    {
      "hooks": {
        "PostToolUse": [
          {
            "matcher": "Skill",
            "hooks": [
              { "type": "command", "command": "bash .claude/hooks/debug-capture.sh" }
            ]
          }
        ]
      }
    }
  3. From inside Claude Code on Windows + Git Bash, execute any slash command whose body uses the Skill tool.

  4. Inspect /tmp/claude-hook-capture.log after the model runs.

Observed: each entry shows non-empty === <date> === (the hook fires) but empty STDIN: and empty ARGS:.

Expected: STDIN: should contain a JSON document with at least .tool_name == "Skill" and .tool_input.skill == "<the skill name>".

Diagnosis

On Windows + Git Bash, Claude Code's subprocess spawn appears to not pipe the JSON payload to bash hooks. Possible causes:

  • A pty/pipe handling difference between Windows and POSIX runtimes.
  • The Git Bash bash.exe invocation path losing the inherited stdin descriptor on subprocess launch.
  • A platform-conditional code path in the harness that skips JSON delivery for cmd.exe-launched hooks.

The hook script is correctly designed per the official contract — this is not a hook authoring issue. On a Linux or macOS runtime, equivalent hooks reportedly receive the payload as documented.

Workaround applied (downstream repo)

We pivoted skill-invocation telemetry capture from the PostToolUse hook to slash-command-internal Bash calls. Each slash command now invokes the increment script inline immediately before invoking the skill — same writer, same arguments, same target file, just executed from inside the model's tool loop where the Bash tool delivery is reliable on this platform.

The hook script itself is not deleted — it is archived with a README.md documenting the restoration recipe for when this upstream issue is resolved.

Why this matters (impact)

The PostToolUse hook channel is intended for governance / observability writes that the model-side workflow shouldn't have to think about. Workflows that use this channel for telemetry, audit logging, or post-hoc validation will silently produce empty data on Windows + Git Bash, making any downstream lifecycle / sufficiency gates that depend on the captured data either inaccurate or no-ops.

Suggested fix shape

  • If the platform constraint is a known limitation: document it explicitly in the hook docs ("PostToolUse stdin payload delivery is not supported on Windows + Git Bash; use slash-command-internal instrumentation instead").
  • Better: deliver the payload via an alternative channel on this platform — e.g., set CLAUDE_TOOL_NAME and CLAUDE_TOOL_INPUT_JSON env vars on the spawned hook process, so portable hook authors can read either source.
  • Best: fix the stdin delivery so the contract is the same on every platform.

Diagnosis transcript

Available on request.

What Should Happen?

Expected behavior (per the official Claude Code hook contract)

The hook's stdin should receive a JSON object describing the tool call that just completed. For the Skill matcher, the document should include at least:

{
  "tool_name": "Skill",
  "tool_input": { "skill": "<skill-name>", "args": "..." }
}

The hook's job is to read stdin, parse with jq, and act on .tool_name + .tool_input.skill.

Error Messages/Logs

Steps to Reproduce

Reproduction (minimal)

  1. Create a tiny capture hook:

    #!/usr/bin/env bash
    # .claude/hooks/debug-capture.sh
    {
      echo "=== $(date) ==="
      echo "STDIN:"
      cat /dev/stdin || true
      echo "ARGS:"
      printf '  %s\n' "$@"
      echo "ENV (CLAUDE_CODE_*):"
      env | grep ^CLAUDE_CODE_ || true
      echo
    } >> /tmp/claude-hook-capture.log 2>&1
  2. Wire it as a PostToolUse hook on the Skill matcher in .claude/settings.json:

    {
      "hooks": {
        "PostToolUse": [
          {
            "matcher": "Skill",
            "hooks": [
              { "type": "command", "command": "bash .claude/hooks/debug-capture.sh" }
            ]
          }
        ]
      }
    }
  3. From inside Claude Code on Windows + Git Bash, execute any slash command whose body uses the Skill tool.

  4. Inspect /tmp/claude-hook-capture.log after the model runs.

Observed: each entry shows non-empty === <date> === (the hook fires) but empty STDIN: and empty ARGS:.

Expected: STDIN: should contain a JSON document with at least .tool_name == "Skill" and .tool_input.skill == "<the skill name>".

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

No response

Claude Code Version

2.1.128

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

IntelliJ IDEA terminal

Additional Information

No response

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] PostToolUse hook on Skill matcher does not receive tool_input payload on Windows + Git Bash