Summary
On Windows, all three AskUserQuestion hooks fail to spawn their backing bin, so question-log.jsonl is never written and /plan-tune has no data at all. Two independent bugs stack, and a third swallows the error so nothing useful reaches hook-errors.log.
This is separate from the permissionDecision: "defer" family (#2035, #2310, #2340, #2006). Those abort the tool call; this one means that even on a build where AUQ works, the logging side is inert on Windows. Fixing defer alone will not make /plan-tune work here.
Environment
- gstack: HEAD
a3259400 (note: VERSION on origin/main reads 1.60.1.0 even though f155481b bumped it to 1.60.2.0 — a later release commit rolled it back, so gstack-update-check reports "up to date" while 31 commits behind)
- OS: Windows 11 Pro 10.0.26200
- bun 1.3.14, Git Bash (
C:\Program Files\Git\bin\bash.exe)
- Install: global git at
~/.claude/skills/gstack, hooks registered via ./setup --plan-tune-hooks
Bug 1 — new URL(import.meta.url).pathname yields a leading-slash path on Windows
hosts/claude/hooks/question-log-hook.ts:209:
const here = path.dirname(new URL(import.meta.url).pathname);
const repoRoot = path.resolve(here, '..', '..', '..');
const bin = path.join(repoRoot, 'bin', 'gstack-question-log');
On Windows URL.pathname is /C:/Users/..., and path.resolve treats the leading slash as root-relative, producing a doubled drive letter:
URL().pathname : /C:/Users/kousu/.claude/skills/gstack/hosts/claude/hooks
repoRoot : C:\C:\Users\kousu\.claude\skills\gstack
bin : C:\C:\Users\kousu\.claude\skills\gstack\bin\gstack-question-log
existsSync(bin): false
Fix: import { fileURLToPath } from 'node:url' and use path.dirname(fileURLToPath(import.meta.url)).
Same pattern in:
hosts/claude/hooks/question-log-hook.ts:209
hosts/claude/hooks/question-preference-hook.ts:226 (question-registry) and :320 (log bin)
hosts/claude/hooks/auq-error-fallback-hook.ts:129
ios-qa/scripts/gen-accessors.ts
Bug 2 — spawnSync cannot exec an extensionless shebang script on Windows
bin/gstack-question-log is a #!/usr/bin/env bash script with no extension. Even with the path corrected, spawnSync(bin, ...) without a shell fails, and shell: true fails too because cmd.exe cannot run it either. Measured with the real, correct path:
A) spawnSync(bin, [payload]) → status undefined, error ENOENT
ENOENT: no such file or directory, uv_spawn '.../bin/gstack-question-log'
B) spawnSync('bash', [bin, payload]) → status 1 (spawns fine; fails downstream)
C) spawnSync(bin, [payload], {shell: true}) → '.../gstack-question-log' is not recognized
as an internal or external command
Only (B) actually starts the script. Fix: spawn via bash on win32, or ship a .cmd/.ps1 shim next to the bin — the same approach the bunx shim needs (~/.gstack/shims/bunx is likewise an extensionless bash script and is unusable outside Git Bash).
Note (B) then exits 1 with gstack-question-log: invalid skill, must be kebab-case, so there may be an additional argv-contract mismatch between the hook's JSON payload and the bin's positional-arg parser. I did not chase that, since it is unreachable until 1 and 2 are fixed.
Bug 3 — the real error is discarded, so this is invisible
question-log-hook.ts:216:
if (res.status !== 0) {
logHookError(`gstack-question-log exited ${res.status}: ${res.stderr || res.stdout}`);
}
res.error is never inspected. On a spawn failure status is undefined and stderr is null, so the only trace is:
2026-07-26T14:41:05.898Z question-log-hook: gstack-question-log exited undefined: null
That message names neither ENOENT nor the bogus doubled-drive path, which makes this near-undiagnosable from the log alone. Suggest logging res.error?.code / res.error.message when res.error is set.
Reproduce
On Windows with the hooks installed:
cd ~/.claude/skills/gstack
printf '%s' '{"hook_event_name":"PostToolUse","tool_name":"AskUserQuestion","tool_input":{"questions":[{"question":"q","header":"h","options":[]}]},"tool_response":{"answers":{"q":"A"}}}' \
| hosts/claude/hooks/question-log-hook
echo "exit=$?" # 0 — hook exits clean
tail -1 ~/.gstack/hook-errors.log # "exited undefined: null"
find ~/.gstack/projects -name question-log.jsonl # empty — never created
The hook exits 0, so nothing surfaces to the user; the log file simply never appears.
Impact
/plan-tune is silently non-functional on Windows — it has no question log to tune against, and question-preference-hook consequently always takes its fall-through path.
Summary
On Windows, all three
AskUserQuestionhooks fail to spawn their backing bin, soquestion-log.jsonlis never written and/plan-tunehas no data at all. Two independent bugs stack, and a third swallows the error so nothing useful reacheshook-errors.log.This is separate from the
permissionDecision: "defer"family (#2035, #2310, #2340, #2006). Those abort the tool call; this one means that even on a build where AUQ works, the logging side is inert on Windows. Fixingdeferalone will not make/plan-tunework here.Environment
a3259400(note:VERSIONonorigin/mainreads1.60.1.0even thoughf155481bbumped it to1.60.2.0— a later release commit rolled it back, sogstack-update-checkreports "up to date" while 31 commits behind)C:\Program Files\Git\bin\bash.exe)~/.claude/skills/gstack, hooks registered via./setup --plan-tune-hooksBug 1 —
new URL(import.meta.url).pathnameyields a leading-slash path on Windowshosts/claude/hooks/question-log-hook.ts:209:On Windows
URL.pathnameis/C:/Users/..., andpath.resolvetreats the leading slash as root-relative, producing a doubled drive letter:Fix:
import { fileURLToPath } from 'node:url'and usepath.dirname(fileURLToPath(import.meta.url)).Same pattern in:
hosts/claude/hooks/question-log-hook.ts:209hosts/claude/hooks/question-preference-hook.ts:226(question-registry) and:320(log bin)hosts/claude/hooks/auq-error-fallback-hook.ts:129ios-qa/scripts/gen-accessors.tsBug 2 —
spawnSynccannot exec an extensionless shebang script on Windowsbin/gstack-question-logis a#!/usr/bin/env bashscript with no extension. Even with the path corrected,spawnSync(bin, ...)without a shell fails, andshell: truefails too becausecmd.execannot run it either. Measured with the real, correct path:Only (B) actually starts the script. Fix: spawn via
bashonwin32, or ship a.cmd/.ps1shim next to the bin — the same approach thebunxshim needs (~/.gstack/shims/bunxis likewise an extensionless bash script and is unusable outside Git Bash).Note (B) then exits 1 with
gstack-question-log: invalid skill, must be kebab-case, so there may be an additional argv-contract mismatch between the hook's JSON payload and the bin's positional-arg parser. I did not chase that, since it is unreachable until 1 and 2 are fixed.Bug 3 — the real error is discarded, so this is invisible
question-log-hook.ts:216:res.erroris never inspected. On a spawn failurestatusisundefinedandstderrisnull, so the only trace is:That message names neither
ENOENTnor the bogus doubled-drive path, which makes this near-undiagnosable from the log alone. Suggest loggingres.error?.code/res.error.messagewhenres.erroris set.Reproduce
On Windows with the hooks installed:
The hook exits 0, so nothing surfaces to the user; the log file simply never appears.
Impact
/plan-tuneis silently non-functional on Windows — it has no question log to tune against, andquestion-preference-hookconsequently always takes its fall-through path.