fix(#580): drop bash.exe wrapper from local .sh hooks on Claude/Windows (#583)

Local-install managed .sh hooks under Claude Code on Windows were wrapped with the absolute Git Bash path; Claude runs the hook string inside Git Bash, so bash tried to exec bash (cannot execute binary file). Centralizes the win32+claude+.sh guard (shellHookOmitsBashRunner) and adds an exported, testable buildLocalShellHookCommand so the local path matches the global path. Closes the #166/#377 regression in the local-install branch. Adds a Windows-covered regression test.

Fixes #580
This commit is contained in:
Tom Boucher
2026-06-01 15:06:41 -04:00
committed by GitHub
parent 692343f8cc
commit 7ed92f8a50
4 changed files with 166 additions and 9 deletions

View File

@@ -16,6 +16,8 @@ const {
projectPersistentPathExportActions,
projectShellCommandText,
projectCodexHookTomlCommand,
shellHookOmitsBashRunner,
buildLocalShellHookCommand,
} = require('../get-shit-done/bin/lib/shell-command-projection.cjs');
// Bidirectional GSD slash-command namespace transformer (#3583).
@@ -1217,7 +1219,7 @@ function buildHookCommand(configDir, hookName, opts) {
// Windows, so wrapping `.sh` hooks with an explicit `bash.exe` path can
// trigger `bash.exe: ... cannot execute binary file`. Emit only the quoted
// script path for Claude on Windows.
if (platform === 'win32' && runtime === 'claude' && isShellHook) {
if (shellHookOmitsBashRunner({ platform, runtime, isShellHook })) {
if (opts.portableHooks) {
const portableBaseDir = projectPortableHookBaseDir({
configDir,
@@ -9699,14 +9701,13 @@ function install(isGlobal, runtime = 'claude', options = {}) {
runtime,
platform: process.platform,
});
const localShellCmd = (hookFile) => localBashRunner === null
? null
: projectShellCommandText({
runnerToken: localBashRunner,
argTokens: [`${localPrefix}/hooks/${hookFile}`],
runtime,
platform: process.platform,
});
const localShellCmd = (hookFile) => buildLocalShellHookCommand({
localPrefix,
hookFile,
bashRunner: localBashRunner,
runtime,
platform: process.platform,
});
const statuslineCommand = isGlobal
? buildHookCommand(targetDir, 'gsd-statusline.js', hookOpts)
: localCmd('gsd-statusline.js');