fix(hooks): AskUserQuestion hooks are a silent no-op on Windows - #2504
Open
rafassousa wants to merge 1 commit into
Open
fix(hooks): AskUserQuestion hooks are a silent no-op on Windows#2504rafassousa wants to merge 1 commit into
rafassousa wants to merge 1 commit into
Conversation
All three Claude Code AskUserQuestion hooks fire, exit 0, and write nothing. On a Windows install they have never captured a single event: every subprocess dies with ENOENT before the bin runs, and the only artifact is a line appended to ~/.gstack/hook-errors.log per question (138 of them on my machine since install, question-log.jsonl absent). Two Windows-only defects, both in path handling: 1. `new URL(import.meta.url).pathname` yields `/C:/Users/...`. The leading slash makes path.resolve treat it as drive-root-relative, so `path.resolve(here, '..', '..', '..')` returns `C:\C:\Users\...`. Nothing under that path exists. This hit all four resolution sites, including the one that loads scripts/question-registry.ts, so door_type enrichment silently degraded to the default as well. 2. `bin/gstack-*` are extensionless bash scripts. Windows has no shebang support, so spawning one directly is ENOENT even with a correct path. Both are fixed once in hosts/claude/hooks/spawn-bin.ts: `repoRoot()` uses fileURLToPath, `runBin()` routes through bash on win32 only. The three hooks call it and drop their duplicated path arithmetic. Unix behavior is unchanged: same direct spawn, same resolved paths. Bun quirk worth recording: on Windows spawnSync returns ENOENT for a backslash exe path containing spaces, so the Git Bash fallback is written with forward slashes. GSTACK_BASH overrides it. test/hooks-windows-paths.test.ts pins both invariants as static tripwires (no URL.pathname self-location, no direct spawnSync in a hook) and adds an end-to-end check that drives question-log-hook with real hook stdin against an isolated GSTACK_STATE_ROOT and asserts the event lands. All 5 tests fail on the pre-fix tree, pass after. The 6 pre-existing gstack-slug failures in test/skill-validation.test.ts on Windows are the same bin-spawn class in the test harness; verified identical with and without this change, left out of scope. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
On Windows, all three Claude Code
AskUserQuestionhooks fire, exit 0, and write nothing. They have never captured a single event on my install. The only artifact is one line appended to~/.gstack/hook-errors.logper question:~/.gstack/projects/<slug>/question-log.jsonlnever got created. Everything downstream of it is dead on Windows: no--deriveinput, no developer profile, andquestion-preference-hookhas no preferences to enforce because nothing was ever logged./plan-tuneinspects an empty store.It fails silently by design (the hooks must never block a session), which is why it went unnoticed.
status: undefinedis the tell: the process never started.Root cause
Two Windows-only defects, both in path handling. Either one alone is fatal.
1.
new URL(import.meta.url).pathnameis not a filesystem path on Windows.The leading slash makes
path.resolvetreat the string as drive-root-relative and rebase it. Nothing under the result exists.fileURLToPathis the correct conversion.This hit all four resolution sites, not just the spawn ones.
loadRegistry()inquestion-preference-hook.tsresolvesscripts/question-registry.tsthe same way, so on Windows the registry silently failed to load and every question'sdoor_typefell back to the default instead of the registered value.2.
bin/gstack-*are extensionless bash scripts.Windows has no shebang support, so
spawnSync(bin, ...)is ENOENT even with a correct path. The bins themselves are already Windows-aware (gstack-question-loghas thecygpath -mhandling from #1950) — they just never got invoked.The fix
One helper,
hosts/claude/hooks/spawn-bin.ts, owns both:repoRoot()—fileURLToPath, so the install root resolves on every platform.runBin(name, args, opts)— resolves underbin/and routes through bash onwin32only.The three hooks call it and drop their duplicated path arithmetic (four copies of the same three lines, minus 19 lines net). Unix behavior is unchanged: same direct spawn, same resolved paths, the
win32branch is the only new code path.One more Windows quirk worth recording: Bun's
spawnSyncreturns ENOENT for a backslash exe path containing spaces, so the Git Bash fallback is written with forward slashes.GSTACK_BASHoverrides it if bash lives somewhere else.Tests
test/hooks-windows-paths.test.ts, in the style ofsetup-windows-fallback.test.ts:new URL(import.meta.url).pathnamefor self-location, or spawns a bin directly instead of throughrunBin.repoRoot()lands on a directory that actually containsbin/andscripts/question-registry.ts.question-log-hookwith real hook stdin against an isolatedGSTACK_STATE_ROOT, then asserts the event landed inquestion-log.jsonlwith the rightsource,user_choice,recommended, andfollowed_recommendation, and thathook-errors.logis empty. This is the one that reproduces the actual user-visible symptom.bun test test/hooks-windows-paths.test.tsVerified on Windows 11, Bun 1.3.14, against
mainat v1.61.0.0.Out of scope, but you should know
test/skill-validation.test.tshas 6gstack-slugfailures on Windows from the same bin-spawn class in the test harness:Per the blame protocol in CLAUDE.md, I checked rather than assumed: the failure list is byte-identical with and without this change, so they are pre-existing and not caused by this PR. Left alone to keep the diff scoped. Happy to send a follow-up that routes the test harness through the same helper if you want it.
Notes
CHANGELOG/VERSIONedit — those are yours at ship time.SKILL.mdtouched.🤖 Generated with Claude Code