Skip to content

fix(claude): widen Git Bash hook fast path to quoted-safe paths - #11497

Closed
Jeonous wants to merge 5 commits into
stablyai:mainfrom
Jeonous:fix/gitbash-hook-fast-path-non-ascii
Closed

Jeonous wants to merge 5 commits into
stablyai:mainfrom
Jeonous:fix/gitbash-hook-fast-path-non-ascii

Conversation

@Jeonous

@Jeonous Jeonous commented Jul 30, 2026

Copy link
Copy Markdown

Fixes #11496.

Summary

On Windows, a home directory containing non-ASCII characters (CJK, Cyrillic, accented
profiles) fails the WINDOWS_GIT_BASH_SAFE_PATH whitelist, so every managed Claude hook
falls back to the PowerShell -EncodedCommand launcher — roughly 640 ms vs 190 ms of
hook overhead per tool call (PreToolUse + PostToolUse both fire; measured 66.2 ms vs
207.4 ms per wrapper call, n=10, details in #11496).

Since #8430 the fast path wraps the script path in quotePosixShellString(), so the
whitelist's original concern (bash splitting on spaces) is already handled by the
quoting. This PR relaxes the gate to reject only control characters:

const WINDOWS_GIT_BASH_SAFE_PATH = /^\P{Cc}+$/u

so non-ASCII profiles — and paths with spaces or shell metacharacters — take the same
fast path as ASCII ones. User-visible effect: lower per-tool-call hook latency for
affected users; the generated command in ~/.claude/settings.json becomes the quoted
if [ -f '…' ] form instead of the encoded PowerShell launcher.

WINDOWS_CMD_SAFE_PATH is deliberately untouched: Codex/Antigravity/Devin spawn the
hook command as argv[0] via CreateProcess rather than through a shell, so that
whitelist is load-bearing (#8430 / #8737). Only the Git Bash branch goes through a shell,
where the quoting provides the safety.

Screenshots

No visual change — this only affects the generated hook command string in
~/.claude/settings.json.

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test
  • pnpm build
  • Added or updated high-quality tests that would catch regressions, or explained why tests were not needed

Test changes:

  • installer-utils.test.ts (wrapWindowsGitBashHookCommand): kept the ASCII
    exact-string case; Jane Doe (spaces) and alice & bob (metacharacters) now assert
    the exact quoted fast-path command instead of the PowerShell fallback; added
    exact-string cases for a non-ASCII home directory and a single-quote path (O'Brien);
    added a control-character case asserting the encoded-launcher fallback still applies.
  • claude/hook-service.test.ts: the end-to-end [Bug]: Codex hooks fail on Windows when user path contains spaces #6078 spaces test now asserts the exact
    quoted fast-path command; removed the now-unused WINDOWS_POWERSHELL_LAUNCHER matcher.

pnpm test note (Windows 11 26200): every test file importing installer-utils or
claude/hook-settings passes (10 files, 144 tests). The full suite reports 179
environment-dependent failures in 54 files on my machine (Windows symlink privileges,
pty/daemon spawn); the only failing file intersecting this change's import graph is
installer-utils.test.ts, whose 3 writeHooksJson symlink tests fail identically on a
pristine checkout (symlink creation needs elevation/Developer Mode on Windows).

AI Review Report

Reviewed with Claude Code. What it checked:

  • Cross-platform (macOS/Linux/Windows): the change sits inside the win32-only path
    (getManagedCommand in src/main/claude/hook-settings.ts); macOS/Linux use
    wrapPosixHookCommand, untouched. No shortcuts, labels, or renderer behavior involved.
  • SSH/remote/local: no SSH-specific logic touched;
    remote-hook-service-installers.test.ts, which exercises the shared wrapper path,
    passes.
  • Agent/integration compatibility: only the Claude/OpenClaude Git Bash wrapper changes.
    Codex/Antigravity/Devin (wrapWindowsCmdHookCommand) and the POSIX wrapper are
    untouched.
  • Performance: strictly widens the fast path; no path that previously took the fast path
    changes behavior.
  • UI quality: not applicable (no UI change).

What it flagged, and what changed as a result: an earlier draft used
/^[^\x00-\x1F\x7F]+$/, which trips oxlint's no-control-regex; switched to the
equivalent Unicode-property form /^\P{Cc}+$/u (which also covers C1 controls). It also
caught that the end-to-end #6078 test still asserted the PowerShell fallback for spaced
paths and had to be updated alongside the unit tests.

Security Audit

  • Command construction: the fast path interpolates the script path only through
    quotePosixShellString (single quotes; embedded ' escaped as '\''). Single quotes
    suppress all POSIX shell expansion, so spaces, metacharacters, and non-ASCII stay
    literal.
  • Control characters are still excluded by the new gate and fall back to the encoded
    PowerShell launcher (covered by a test).
  • No change to what gets executed: same managed claude-hook.cmd, same drain-on-missing
    behavior; only the wrapper form changes for paths the whitelist previously excluded.
  • No input handling, auth, secrets, dependency, or IPC surface touched.

Notes

  • Windows-specific by construction; guarded by the existing platform check.
  • Caveat: measured on an ANSI-code-page-65001 (UTF-8) system. I have not verified
    CP949/CP936 defaults; the commit that introduced the gate (801effa) and its tests
    indicate spaces/metacharacters were the concern rather than encoding, but flagging for
    reviewer judgment.

Fixes #11496.

The fast-path gate from #7131 whitelists [A-Za-z0-9_.:/~-], so any home
directory with non-ASCII characters (CJK, Cyrillic, accented profiles) -
and any path with spaces - falls back to the encoded PowerShell launcher,
tripling per-hook overhead (measured 66ms vs 207ms per wrapper call;
PreToolUse and PostToolUse both fire on every tool call).

Since #8430 the fast path wraps the script path in single quotes, which
keeps spaces, metacharacters, and non-ASCII literal. The gate now only
needs to exclude control characters.

Leave WINDOWS_CMD_SAFE_PATH untouched: Codex/Antigravity/Devin spawn the
hook as argv[0], so that whitelist is load-bearing (#8430/#8737).
@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Git Bash hook path handling now uses a Unicode-aware safety check that permits whitespace and excludes control characters and parser-sensitive metacharacters. Tests cover quoted paths, Unicode segments, embedded single quotes, shell execution, and stdin draining. Unsafe paths still use the encoded launcher. Managed-hook tests assert the exact quoted command and POSIX stdin-drain fallback.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: widening the Git Bash hook fast path for safely quoted paths.
Description check ✅ Passed The description includes all required sections and documents the change, testing, review coverage, security audit, and platform notes.
Linked Issues check ✅ Passed The implementation satisfies issue #11496 by enabling quoted-safe non-ASCII paths while preserving fallbacks and leaving WINDOWS_CMD_SAFE_PATH unchanged.
Out of Scope Changes check ✅ Passed All code and test changes support the linked issue and PR objectives; no unrelated changes are evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown

Greptile Summary

This PR widens the WINDOWS_GIT_BASH_SAFE_PATH gate from /^[A-Za-z0-9_.:/~-]+$/ to /^\P{Cc}+$/u, letting paths with spaces, shell metacharacters, and non-ASCII characters (e.g. CJK user profiles) take the quoted fast path instead of the ~3× slower PowerShell -EncodedCommand launcher. The change is safe because the fast path already routes the path through quotePosixShellString, which uses POSIX single-quoting to suppress all shell interpretation.

  • installer-utils.ts: The regex change is minimal and correctly scoped to the Git Bash branch; WINDOWS_CMD_SAFE_PATH (used by Codex/Devin's argv[0] spawn path) is intentionally left unchanged.
  • installer-utils.test.ts: Existing space/metachar tests are updated from "expect PowerShell fallback" to "expect exact quoted command"; new tests add non-ASCII, single-quote, and control-character coverage.
  • hook-service.test.ts: The end-to-end #6078 test is updated to assert the quoted fast-path form instead of the PowerShell launcher regex.

Confidence Score: 5/5

Safe to merge; the change is a minimal regex relaxation in the Windows Git Bash branch only, and the quoting layer that neutralizes all shell-special characters was already in place.

The widened regex is applied to bashPath before it is passed to quotePosixShellString, which wraps the path in POSIX single quotes and escapes embedded single quotes with the standard POSIX trick. Single-quoted strings in bash suppress all shell expansion, so spaces, &, $, CJK characters, and other non-control characters are all handled safely. Control characters (Unicode Cc: U+0000-U+001F, U+007F, U+0080-U+009F) are still rejected and fall back to the PowerShell encoded launcher, covered by a test. WINDOWS_CMD_SAFE_PATH for Codex/Devin argv[0] spawn is untouched. Test coverage across unit and end-to-end layers is thorough.

Files Needing Attention: No files require special attention; the only changed production code is the single-line regex in installer-utils.ts.

Important Files Changed

Filename Overview
src/main/agent-hooks/installer-utils.ts Core change: WINDOWS_GIT_BASH_SAFE_PATH widened to /^\P{Cc}+$/u; wrapWindowsGitBashHookCommand logic and quotePosixShellString are unchanged and correct.
src/main/agent-hooks/installer-utils.test.ts Tests updated to cover fast-path for spaces, metacharacters, non-ASCII, single-quote escaping, and control-character fallback; coverage is comprehensive.
src/main/claude/hook-service.test.ts End-to-end #6078 test correctly updated to assert the quoted fast-path command; unused WINDOWS_POWERSHELL_LAUNCHER matcher removed.

Reviews (1): Last reviewed commit: "fix(claude): widen Git Bash hook fast pa..." | Re-trigger Greptile

The quoting contract this fix relies on had only string-equality coverage;
every executable Git Bash assertion is win32-only and PR CI runs tests on
Linux, so a dropped quote would ship green. Run the generated command
through /bin/sh for spaced, metacharacter, non-ASCII, and single-quote
homes, plus the missing-script drain.

Also drop the stale claim that the Claude launcher stays PowerShell-encoded.
Bash's single quotes are not the last parser. Executing a .cmd makes Git
Bash re-launch it through cmd.exe (COMSPEC), which parses the path again
with the quoting already consumed. Measured on Git Bash 2.55 / Windows:
a home containing & ^ ( ) ; , = or %VAR% exits 1 on every hook call,
where the encoded launcher runs it correctly. ! is safe today but
expands under a registry-enabled DelayedExpansion, so it is gated too.

Spaces, quotes, and non-ASCII (CJK, Cyrillic, accented) profiles are
unaffected and keep the fast path — verified on a real ACP=1252 host,
which also closes the PR's open question about non-UTF-8 code pages.
The premise the fast path rests on - that Claude's Git Bash runner can
execute a forward-slash .cmd directly - was dropped when the gate comment
was rewritten, leaving no way to reconstruct why the exclusion set is what
it is. Restore it and state the bash -> cmd.exe hand-off explicitly.

Also stop calling '!' a cmd.exe metacharacter (it is inert without
DelayedExpansion), stop implying the POSIX execution block covers Windows,
and assert tmpdir passes the gate so a gated tmpdir cannot masquerade as a
quoting failure.
@brennanb2025

Copy link
Copy Markdown
Contributor

Thanks for this — the diagnosis is right, and the measurement in #11496 is genuinely useful. I've pushed three commits to your branch so this is landable; details below so nothing is a surprise.

The one behavior change I had to make. I tested the widened gate on a real Windows host (Git Bash 2.55, ANSI code page 1252) and hit a case the quoting argument doesn't cover: bash's single quotes aren't the last parser. Running a .cmd makes Git Bash re-launch it through cmd.exe via COMSPEC, which parses the path again after the shell's quoting is already gone. Measured, with a home directory containing each character:

  • fast path works: spaces, ', $, backtick, #, ~, @, {}, [], +, and non-ASCII (CJK, Cyrillic, accented)
  • fast path exits 1 on every hook call: & ^ ( ) ; , = and %VAR%

So the gate is now /^[^\p{Cc}&^();,=%!]+$/u — control characters plus the set cmd.exe re-parses. (! is inert today but expands under a registry-enabled DelayedExpansion, so it's excluded too.)

Your actual fix is intact. CJK, Cyrillic, and accented profiles still take the fast path — confirmed on that same code-page-1252 machine, which also answers the CP949/CP936 question you flagged in your notes. Good instinct flagging it; it turned out fine.

Tests. I added cases that execute the generated command through a real shell rather than asserting the string. Every existing Git Bash assertion is skipIf(platform !== 'win32') and PR CI runs the vitest shards on Linux only, so the quoting contract had no executed coverage anywhere — a dropped quote would have shipped green.

Two things I deliberately left out of this PR so it stays scoped to one change:

  • WINDOWS_CMD_SAFE_PATH is still ASCII-only, so a non-ASCII profile keeps paying the slow launcher for Codex, Antigravity, Devin, Droid, Grok, Cursor, Kimi and Command Code. [Bug]: Non-ASCII home paths skip the Git Bash hook fast path, tripling per-hook overhead #11496 is only fully fixed once that's addressed too — worth a follow-up.
  • A % in the profile path fails the PowerShell fallback as well, so those users have broken hooks today regardless of this change. Separate bug; noting it here so it isn't lost.

One gap worth stating plainly: there's no Windows vitest job in PR CI, so the cmd.exe half of this is pinned by a hand-verified denylist rather than an automated gate.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/agent-hooks/installer-utils.test.ts (1)

583-587: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Shorten the explanatory test comments.

Keep only the parser-chain or test-invariant detail needed to explain each case. Remove measured behavior and repeated rationale from the comments.

As per coding guidelines, comments must be concise, limited to non-obvious information, and preferably one line.

Also applies to: 622-626, 635-636, 644-646

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5e5653c4-d641-47b3-af90-cb6da16626d8

📥 Commits

Reviewing files that changed from the base of the PR and between efca463 and d56f233.

📒 Files selected for processing (3)
  • src/main/agent-hooks/installer-utils.test.ts
  • src/main/agent-hooks/installer-utils.ts
  • src/main/claude/hook-service.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/agent-hooks/installer-utils.ts
  • src/main/claude/hook-service.test.ts

…-path-non-ascii

# Conflicts:
#	src/main/agent-hooks/installer-utils.test.ts
#	src/main/agent-hooks/installer-utils.ts
#	src/main/claude/hook-service.test.ts
@OrcaWin

OrcaWin commented Aug 12, 2026 •

Copy link
Copy Markdown
Collaborator

Windows low-spec QA — PR #11497 (Grok)

Commit validated: 58db93e90b4539100c01b151de70a997d1f0e735 (matches live refs/pull/11497/head)

Tree vs main parent

  • Base 96c17b44d51015fbf88bceb640f8b64c2a0f4d7e and head share identical tree 061d521275e58e916ca40f16c4451ab5e348ac42
  • git diff base head is empty — PR is content-empty / superseded by main

Superseding runtime-home hook (on main / this tree)

  • Claude managed hooks use wrapRuntimeHomeHookCommand (src/main/agent-hooks/runtime-home-hook-command.ts) via $HOME/.orca/agent-hooks/*.cmd
  • Non-ASCII Windows homes take the fast path (direct .cmd); cmd.exe metachar homes (& ^ ( ) ; , = % !) fall back to encoded PowerShell
  • Platform selection uses OSTYPE case — no uname process per hook

Focused tests (Windows)

pnpm exec vitest run src/main/claude/hook-service.test.ts  → 14/14 pass
pnpm exec vitest run src/main/agent-hooks/installer-utils.test.ts -t wrapRuntimeHomeHookCommand|wrapWindowsCmdHookCommand --testTimeout=30000
  → 8/8 pass including "keeps common Windows profiles on the fast path"

Unrelated env failures when running full installer-utils without filter: 3 symlink EPERM (no Developer Mode), 3 default 5s timeouts that pass at 30s.

Real Git Bash path check

Executed generated command under C:\Program Files\Git\bin\bash.exe:

  • non-ASCII home (…/destination 国際 profile) → exit 7 from .cmd, no powershell.exe in set -x trace
  • ASCII home → exit 11
  • metachar home (user&name) → exit 13 via encoded fallback
  • invariants: OSTYPE case present, no uname, no absolute paths

Electron identity (CDP 9341, isolated userData)

{"name":"Orca: 58db93e90b","isDev":true,"devLabel":"pr-11497-grok-windows-qa @ 58db93e90b","devBranch":"58db93e90b","devWorktreeName":"pr-11497-grok-windows-qa","devRepoRoot":"C:\\Users\\neil\\orca\\workspaces\\orca\\pr-11497-grok-windows-qa"}

Navigated Settings → Agents; Agent status hooks toggle visible and ON. Console: 0 errors (dev CSP/tooltip warnings only).

Screenshots (not committed)

Settings → Agents with Agent status hooks:

Settings Agents / Agent status hooks

Settings Agents full page

Gist: https://gist.github.com/OrcaWin/cda9707c6beaa9d45645e2b7e0a17b37

Recommendation

Close as superseded / do not land — empty diff vs main; original fix is preserved by the runtime-home hook path already on main. No code changes made during this validation.

Note: dev Electron on this host hits EPERM fsync when creating a fresh profile index (fsyncFileSync opens 'r' on Windows). Pre-seeding the index unblocks startup; unrelated to this PR.

Superseding fix: #13442 — fix(claude): make managed hook paths portable

@brennanb2025

Copy link
Copy Markdown
Contributor

Thanks for the thoughtful report, performance measurements, and implementation — the diagnosis here was correct and genuinely useful.

A broader runtime-home solution landed independently in #13442 and now covers this non-ASCII Windows-home case while preserving the safe fast path. After updating this branch from main, #11497 has no remaining diff, so I’m closing it as no longer necessary. Thanks again for contributing this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Non-ASCII home paths skip the Git Bash hook fast path, tripling per-hook overhead

4 participants