0
Fork 0
mirror of https://github.com/obra/superpowers.git synced 2026-09-25 22:09:05 +00:00

SessionStart hook produces no output on Windows (Git Bash shebang issue) #354

Closed
opened 2026-01-24 12:51:52 +00:00 by kuges · 8 comments
kuges commented 2026-01-24 12:51:52 +00:00 (Migrated from github.com)

Description

The session-start.sh hook fails to produce any output when executed on Windows with Git Bash, causing the "SessionStart:startup hook error" message to appear at startup.

Environment

  • OS: Windows 10/11
  • Shell: Git Bash 5.2.37
  • Claude Code: 2.1.19
  • Superpowers version: 4.1.1

Problem

When Claude Code runs the SessionStart hook on Windows, it executes:

"C:\Program Files\Git\bin\bash.exe" "...\superpowers\4.1.1\hooks\session-start.sh"

The script has a shebang line #!/usr/bin/env bash, and when executed this way on Windows/Git Bash, no stdout is produced even though the script exits with code 0.

Investigation Results

I tested extensively and found:

Command Result
bash script.sh (with shebang) No output
source script.sh Works correctly
`tail -n +2 script.sh bash` (skip shebang)
bash script.sh (without shebang) Works correctly

This appears to be a quirk of how Git Bash on Windows handles scripts with shebang lines when invoked as bash script.sh.

Debug Log Evidence

From Claude Code debug log:

[DEBUG] Getting matching hook commands for SessionStart with query: startup
[DEBUG] Found 1 hook matchers in settings
[DEBUG] Matched 1 unique hooks for query "startup" (1 before deduplication)
[DEBUG] Hook output does not start with {, treating as plain text

The hook runs but produces empty/non-JSON output, triggering the error display.

Suggested Fixes

Option 1: Remove the shebang line

Since Claude Code 2.1.x already prepends bash when running .sh files on Windows, the shebang is redundant and causes the issue.

Option 2: Use a wrapper approach

Have the hook command use bash -c 'source ...' instead of directly invoking the script.

Option 3: Add Windows-specific detection

Check for Windows in hooks.json and use a different command format.

Additional Context

The existing run-hook.cmd polyglot wrapper was deprecated per its comments because Claude Code 2.1.x changed the execution model. However, the new model still has issues with shebang handling on Windows.

## Description The `session-start.sh` hook fails to produce any output when executed on Windows with Git Bash, causing the "SessionStart:startup hook error" message to appear at startup. ## Environment - **OS:** Windows 10/11 - **Shell:** Git Bash 5.2.37 - **Claude Code:** 2.1.19 - **Superpowers version:** 4.1.1 ## Problem When Claude Code runs the SessionStart hook on Windows, it executes: ``` "C:\Program Files\Git\bin\bash.exe" "...\superpowers\4.1.1\hooks\session-start.sh" ``` The script has a shebang line `#!/usr/bin/env bash`, and when executed this way on Windows/Git Bash, **no stdout is produced** even though the script exits with code 0. ## Investigation Results I tested extensively and found: | Command | Result | |---------|--------| | `bash script.sh` (with shebang) | No output | | `source script.sh` | Works correctly | | `tail -n +2 script.sh | bash` (skip shebang) | Works correctly | | `bash script.sh` (without shebang) | Works correctly | This appears to be a quirk of how Git Bash on Windows handles scripts with shebang lines when invoked as `bash script.sh`. ## Debug Log Evidence From Claude Code debug log: ``` [DEBUG] Getting matching hook commands for SessionStart with query: startup [DEBUG] Found 1 hook matchers in settings [DEBUG] Matched 1 unique hooks for query "startup" (1 before deduplication) [DEBUG] Hook output does not start with {, treating as plain text ``` The hook runs but produces empty/non-JSON output, triggering the error display. ## Suggested Fixes ### Option 1: Remove the shebang line Since Claude Code 2.1.x already prepends `bash` when running `.sh` files on Windows, the shebang is redundant and causes the issue. ### Option 2: Use a wrapper approach Have the hook command use `bash -c 'source ...'` instead of directly invoking the script. ### Option 3: Add Windows-specific detection Check for Windows in hooks.json and use a different command format. ## Additional Context The existing `run-hook.cmd` polyglot wrapper was deprecated per its comments because Claude Code 2.1.x changed the execution model. However, the new model still has issues with shebang handling on Windows.
bkerf commented 2026-01-24 14:25:37 +00:00 (Migrated from github.com)

+1

+1
bkerf commented 2026-01-24 14:25:37 +00:00 (Migrated from github.com)

+1

+1
sgeraldes commented 2026-01-25 03:58:31 +00:00 (Migrated from github.com)

Root Cause Analysis

I investigated this issue in depth and found two distinct problems causing the SessionStart hook to fail on Windows:

Problem 1: Path Mangling (Primary Cause of "No such file or directory")

When Claude Code executes the hook, it sets CLAUDE_PLUGIN_ROOT to a Windows path with backslashes (e.g., C:\Users\Sebastian\.claude\plugins\...). When this is passed through bash command execution, the backslashes are interpreted as escape characters and stripped:

# Reproduction:
$ PLUGIN_ROOT='C:\Users\Sebastian\.claude\plugins\cache\claude-plugins-official\superpowers\4.1.1'
$ bash -c "$PLUGIN_ROOT/hooks/session-start.sh"
bash: line 1: C:UsersSebastian.claudepluginscacheclaude-plugins-officialsuperpowers4.1.1/hooks/session-start.sh: No such file or directory

The path becomes C:UsersSebastian... instead of C:\Users\Sebastian\....

Problem 2: Shebang stdout suppression (Secondary Issue)

Even when the path is correct, Git Bash on Windows suppresses stdout when running scripts with shebang lines via bash script.sh:

$ bash session-start.sh        # → No output, exit code 0
$ source session-start.sh      # → Works correctly, produces JSON
$ bash -c 'source session-start.sh'  # → Also works

This appears to be a quirk of how Git Bash handles the #!/usr/bin/env bash shebang when the script is invoked as bash script.sh.

Fix

I've submitted #356 which replaces the bash script with a cross-platform Node.js equivalent that:

  • Uses __dirname for reliable path resolution (avoiding the CLAUDE_PLUGIN_ROOT backslash issue)
  • Works identically on Windows, macOS, and Linux
  • Produces proper JSON output for the Claude Code hook system

This follows the same pattern used by other plugins (like everything-claude-code) that work correctly on Windows.

## Root Cause Analysis I investigated this issue in depth and found **two distinct problems** causing the SessionStart hook to fail on Windows: ### Problem 1: Path Mangling (Primary Cause of "No such file or directory") When Claude Code executes the hook, it sets `CLAUDE_PLUGIN_ROOT` to a Windows path with backslashes (e.g., `C:\Users\Sebastian\.claude\plugins\...`). When this is passed through bash command execution, the backslashes are interpreted as escape characters and stripped: ```bash # Reproduction: $ PLUGIN_ROOT='C:\Users\Sebastian\.claude\plugins\cache\claude-plugins-official\superpowers\4.1.1' $ bash -c "$PLUGIN_ROOT/hooks/session-start.sh" bash: line 1: C:UsersSebastian.claudepluginscacheclaude-plugins-officialsuperpowers4.1.1/hooks/session-start.sh: No such file or directory ``` The path becomes `C:UsersSebastian...` instead of `C:\Users\Sebastian\...`. ### Problem 2: Shebang stdout suppression (Secondary Issue) Even when the path is correct, Git Bash on Windows suppresses stdout when running scripts with shebang lines via `bash script.sh`: ```bash $ bash session-start.sh # → No output, exit code 0 $ source session-start.sh # → Works correctly, produces JSON $ bash -c 'source session-start.sh' # → Also works ``` This appears to be a quirk of how Git Bash handles the `#!/usr/bin/env bash` shebang when the script is invoked as `bash script.sh`. ### Fix I've submitted #356 which replaces the bash script with a cross-platform Node.js equivalent that: - Uses `__dirname` for reliable path resolution (avoiding the CLAUDE_PLUGIN_ROOT backslash issue) - Works identically on Windows, macOS, and Linux - Produces proper JSON output for the Claude Code hook system This follows the same pattern used by other plugins (like `everything-claude-code`) that work correctly on Windows.
sgeraldes commented 2026-01-25 03:58:31 +00:00 (Migrated from github.com)

Root Cause Analysis

I investigated this issue in depth and found two distinct problems causing the SessionStart hook to fail on Windows:

Problem 1: Path Mangling (Primary Cause of "No such file or directory")

When Claude Code executes the hook, it sets CLAUDE_PLUGIN_ROOT to a Windows path with backslashes (e.g., C:\Users\Sebastian\.claude\plugins\...). When this is passed through bash command execution, the backslashes are interpreted as escape characters and stripped:

# Reproduction:
$ PLUGIN_ROOT='C:\Users\Sebastian\.claude\plugins\cache\claude-plugins-official\superpowers\4.1.1'
$ bash -c "$PLUGIN_ROOT/hooks/session-start.sh"
bash: line 1: C:UsersSebastian.claudepluginscacheclaude-plugins-officialsuperpowers4.1.1/hooks/session-start.sh: No such file or directory

The path becomes C:UsersSebastian... instead of C:\Users\Sebastian\....

Problem 2: Shebang stdout suppression (Secondary Issue)

Even when the path is correct, Git Bash on Windows suppresses stdout when running scripts with shebang lines via bash script.sh:

$ bash session-start.sh        # → No output, exit code 0
$ source session-start.sh      # → Works correctly, produces JSON
$ bash -c 'source session-start.sh'  # → Also works

This appears to be a quirk of how Git Bash handles the #!/usr/bin/env bash shebang when the script is invoked as bash script.sh.

Fix

I've submitted #356 which replaces the bash script with a cross-platform Node.js equivalent that:

  • Uses __dirname for reliable path resolution (avoiding the CLAUDE_PLUGIN_ROOT backslash issue)
  • Works identically on Windows, macOS, and Linux
  • Produces proper JSON output for the Claude Code hook system

This follows the same pattern used by other plugins (like everything-claude-code) that work correctly on Windows.

## Root Cause Analysis I investigated this issue in depth and found **two distinct problems** causing the SessionStart hook to fail on Windows: ### Problem 1: Path Mangling (Primary Cause of "No such file or directory") When Claude Code executes the hook, it sets `CLAUDE_PLUGIN_ROOT` to a Windows path with backslashes (e.g., `C:\Users\Sebastian\.claude\plugins\...`). When this is passed through bash command execution, the backslashes are interpreted as escape characters and stripped: ```bash # Reproduction: $ PLUGIN_ROOT='C:\Users\Sebastian\.claude\plugins\cache\claude-plugins-official\superpowers\4.1.1' $ bash -c "$PLUGIN_ROOT/hooks/session-start.sh" bash: line 1: C:UsersSebastian.claudepluginscacheclaude-plugins-officialsuperpowers4.1.1/hooks/session-start.sh: No such file or directory ``` The path becomes `C:UsersSebastian...` instead of `C:\Users\Sebastian\...`. ### Problem 2: Shebang stdout suppression (Secondary Issue) Even when the path is correct, Git Bash on Windows suppresses stdout when running scripts with shebang lines via `bash script.sh`: ```bash $ bash session-start.sh # → No output, exit code 0 $ source session-start.sh # → Works correctly, produces JSON $ bash -c 'source session-start.sh' # → Also works ``` This appears to be a quirk of how Git Bash handles the `#!/usr/bin/env bash` shebang when the script is invoked as `bash script.sh`. ### Fix I've submitted #356 which replaces the bash script with a cross-platform Node.js equivalent that: - Uses `__dirname` for reliable path resolution (avoiding the CLAUDE_PLUGIN_ROOT backslash issue) - Works identically on Windows, macOS, and Linux - Produces proper JSON output for the Claude Code hook system This follows the same pattern used by other plugins (like `everything-claude-code`) that work correctly on Windows.
obra commented 2026-01-25 07:20:14 +00:00 (Migrated from github.com)

Node isn't guaranteed to be installed anymore, but bash is, so a node script, sadly, isn't a plausible solution.

Node isn't guaranteed to be installed anymore, but bash is, so a node script, sadly, isn't a plausible solution.
obra commented 2026-01-25 07:20:14 +00:00 (Migrated from github.com)

Node isn't guaranteed to be installed anymore, but bash is, so a node script, sadly, isn't a plausible solution.

Node isn't guaranteed to be installed anymore, but bash is, so a node script, sadly, isn't a plausible solution.
obra commented 2026-02-05 19:57:00 +00:00 (Migrated from github.com)

Partial fix update

Two of the three Windows SessionStart problems have been fixed on main:

  • async: true (961052e): Hook failures no longer freeze the terminal
  • O(n) escape_for_json (038abed): ~7x faster, eliminates performance-related timeouts

The path mangling problem described in the root cause analysis comment (CLAUDE_PLUGIN_ROOT backslash stripping) is tracked at #420 (canonical issue, upstream at anthropics/claude-code#23204).

Still open here: The Git Bash shebang stdout suppression (bash script.sh with shebang → no output) is a distinct issue not addressed by the above fixes. Keeping this open to track that.

## Partial fix update Two of the three Windows SessionStart problems have been fixed on main: - **`async: true`** (`961052e`): Hook failures no longer freeze the terminal - **O(n) `escape_for_json`** (`038abed`): ~7x faster, eliminates performance-related timeouts The **path mangling** problem described in the root cause analysis comment (CLAUDE_PLUGIN_ROOT backslash stripping) is tracked at **#420** (canonical issue, upstream at anthropics/claude-code#23204). **Still open here:** The Git Bash shebang stdout suppression (`bash script.sh` with shebang → no output) is a distinct issue not addressed by the above fixes. Keeping this open to track that.
obra commented 2026-02-05 20:15:20 +00:00 (Migrated from github.com)

Investigation: shebang + onecmd interaction

We investigated the shebang stdout suppression in depth. The root cause is Claude Code's onecmd (-t) bash option (anthropics/claude-code#19217).

When CC runs bash session-start.sh with -t active, bash reads the shebang line (#\!/usr/bin/env bash) and counts it as the "one command" — then exits before executing any script body. This is why removing the shebang fixes it: without it, set -euo pipefail becomes the one command that runs, and the rest is lost too, but differently.

We tested set +t as a workaround, but it does not work when a shebang is present — the shebang consumes the one-command slot before set +t ever executes:

Script bash -t result
shebang + set +t + echo no output
no shebang + set +t as first line works

Conclusion: This is not fixable from the plugin side without removing the shebang (which would break macOS/Linux) or adding a janky wrapper layer. This is an upstream CC bug — CC should not set onecmd when executing hook scripts.

The async: true fix (961052e) means the terminal won't freeze, but the hook still produces no output on affected Windows systems, so skills don't load.

## Investigation: shebang + onecmd interaction We investigated the shebang stdout suppression in depth. The root cause is Claude Code's `onecmd` (`-t`) bash option ([anthropics/claude-code#19217](https://github.com/anthropics/claude-code/issues/19217)). When CC runs `bash session-start.sh` with `-t` active, bash reads the shebang line (`#\!/usr/bin/env bash`) and counts it as the "one command" — then exits before executing any script body. This is why removing the shebang fixes it: without it, `set -euo pipefail` becomes the one command that runs, and the rest is lost too, but differently. We tested `set +t` as a workaround, but it **does not work** when a shebang is present — the shebang consumes the one-command slot before `set +t` ever executes: | Script | `bash -t` result | |--------|-----------------| | shebang + `set +t` + echo | no output | | no shebang + `set +t` as first line | works | **Conclusion:** This is not fixable from the plugin side without removing the shebang (which would break macOS/Linux) or adding a janky wrapper layer. This is an upstream CC bug — CC should not set `onecmd` when executing hook scripts. The `async: true` fix (`961052e`) means the terminal won't freeze, but the hook still produces no output on affected Windows systems, so skills don't load.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
skills/obra-superpowers#354
No description provided.