Skip to content

Windows: AUQ hooks can never spawn gstack-question-log (doubled drive letter from URL().pathname + spawnSync of extensionless bash script) → /plan-tune has no data #2356

Description

@kousundberg

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions