Repository navigation
fix(hooks): render CodeBuddy's Windows hooks in POSIX, not cmd.exe - #989
Open
CarlosWonMore wants to merge 1 commit into
Open
CarlosWonMore wants to merge 1 commit into
CarlosWonMore wants to merge 1 commit into
Conversation
CodeBuddy runs a hook's `command` string through Git Bash on Windows — it dropped PowerShell/CMD for hooks in 2.52.4 and forces Git Bash since 2.55.1 — so the cmd-syntax commands added in Tencent#637 misbehave there: - `2>nul` is a file redirect for bash, not the null device, so every hook invocation leaves a 0-byte file named `nul` in the hook's working directory. PostToolUse (matcher `*`) fires after every tool call, so it reappears continuously in the project root, and a reserved device name cannot be removed with `del` or Explorer. - `set "PATH=…;%PATH%"` never prepends PATH under bash. - `|| exit /b 0` makes bash exit 2 on a failing dispatch, which CodeBuddy reads as `allowed:false` and would block a UserPromptSubmit with. The project gate is affected the same way: `cd| findstr … >nul` run by Git Bash errors on `findstr`, exits non-zero and leaves another `nul`. Render the POSIX wrapper form for CodeBuddy, which leaves the cmd renderer unused: drop it, and resolve CodeBuddy's shell to Git Bash the way WorkBuddy's resolver already asks for its bundled MSYS sh. Null when Git Bash is absent, which skips injection with the existing warning instead of installing hooks that could never run. Legacy cmd gates stay recognised on read, so entries written by 0.26.0 are replaced on the next pull rather than duplicated.
|
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.
Summary
CodeBuddy runs its Windows hook commands through Git Bash, but teamai rendered them in cmd.exe syntax. Run by Git Bash,
2>nulis a file redirect, not the null device, so every hook invocation created a real 0-byte file namednulin the hook's working directory — and because thePostToolUsewildcard hook fires after every tool call, the project root got one continuously. The same rendering also made the PATH prepend a no-op and turned the deliberate "always exit 0"|| exit /b 0into an exit 2, which is exactly what makes CodeBuddy read a hook asallowed:false.This makes CodeBuddy use the POSIX renderings — built-in dispatch and the team-hook project gate — like every other tool, and resolves its shell to Git Bash.
Fixes #932.
Type of Change
What changed
Drop the cmd.exe renderer.
toolUsesCmdShell()and the cmd branch ofgateTeamHookCommand()are gone; every tool now gets the POSIX gate. CodeBuddy resolves its hook shell through the Git Bash it already requires on Windows (resolveCodebuddyShell()→findGitBashWindows()), returning null — which correctly skips injection with the existing warning — when Git Bash is absent, instead of installing hooks that could never run.Keep the legacy cmd gate readable.
cmdProjectGate()stays as a read-side recogniser inisGatedForProject(): entries written by 0.26.0 are still on disk, and matching them is what lets a re-render replace the old gate rather than stack a second one on top.Preserve the Cursor/Copilot wrapper.
skipWhenAnotherHostLoadsClaudeSettings()(#950) is orthogonal and still keyed ontool; only the gate lost its per-tool form. Resolving the conflict kept it intact.Test Plan
npx tsc --noEmit— passesnpm run lint— 0 warnings, 0 errors on 811 files. Note:--type-aware(tsgolint) could not run in my sandbox — its subprocess fails to spawn withos error 231(all pipe instances busy), which is an environment limit rather than a code signal, so that half is unverified here.npx vitest runon the 4 test files touching this area — 66 passed, 3 skipped, 0 failed (hooks-golden,hooks-shell-check,hooks-windows-bash,hooks-reconcile-scope). The full suite is not a usable gate on Windows; unrelated pre-existing/bin/sh,chmodand file-mode cases fail identically onmain.Environment-independent gate tests
hooks-reconcile-scope.test.tsasserted the rendered gate by reading back~/.codebuddy/settings.json, but it never staged a shell for the mockedwin32platform — so whether the case ran at all depended on the developer's Git install location. On a machine where Git lives outsideProgramFiles/LOCALAPPDATAand can only be found via theHKLM\SOFTWARE\GitForWindowsregistry key,findGitBashWindows()returned null,skipToolsWithoutShell()dropped both tools, and the read failed with ENOENT.The block now clears the three environment variables and stages both candidates under the mocked home (CodeBuddy's
bash.exe, WorkBuddy's PortableGitsh.exe), matching whathooks-shell-check.test.tsalready does. The gate assertions are now the same on any developer box and on CI.Real-CLI end-to-end verification
Built with
npm run build, then ran the built CLI against a throwaway HOME (a temp dir) with an already-initialised config and a local team repo, so the real injection path ran:codebuddyis no longer skipped (its Git Bash resolves), and<sandbox>\.codebuddy\settings.jsonnow holds:Before this change the same run produced (captured from a real Windows machine, and pinned by the old assertions):
2>nulunder Git Bash creates a file namednul;set "PATH=…;%PATH%"never prepends anything;|| exit /b 0exits 2 on a failing dispatch, which CodeBuddy reads asallowed:false.The project gate moved the same way — from
to
Notes for review
allowed:falseand block everyUserPromptSubmitoutside the project.${gate} || exit /b 0 && (…): the||would swallow a genuine payload failure along with the mismatch. With only one rendering left, the POSIXif …; then …; figives that pass-through directly.