You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit cd1ae35
Browse filesBrowse the repository at this point in the historyBrowse files
Keep unknown binaries in Git Bash (fixes "jq: command not found") — v0.1.2 (#1)
* Keep unknown binaries in Git Bash instead of routing them to PowerShell
A binary missing from POSIX_SAFE_BINS pulled the entire command chain out of
the Git Bash pass-through path and into PowerShell. PowerShell only resolves
the Windows PATH, not the MSYS /usr/bin tree, so an installed and working tool
reported as "command not found" — `jq` being the common case. The effect
cascaded across the chain: in `cat x.json | jq …` the `cat` segment also ended
up in PowerShell, where it is an alias for Get-Content and pipes objects rather
than bytes.
An allowlist can never be complete, so the default is inverted: an unknown
binary is assumed to be an ordinary executable and passes through to Git Bash.
Only PowerShell-shaped names are still routed to PowerShell — Verb-Noun cmdlets
and *.ps1 scripts, via the new looksLikePowerShell(). This matches what the
README already documented for the passthrough class.
POSIX_SAFE_BINS additionally gains the tools that show up in everyday chains
(jq, yq, rg, fd, tar, openssl, ssh, make, sqlite3, aws, az, terraform, …) so the
fast path stays explicit and self-documenting.
The deny-list and WIN32_TRANSPILE_FORCE_PS keep precedence over the new default;
both are covered by tests. 40 -> 47 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add CI and release workflows
The repository had no automation: tests only ever ran on a maintainer's machine,
and a release was a manual build-tag-publish sequence with no guard against
shipping a tag whose version disagreed with package.json.
- ci.yml runs the suite on Node 20/22/24 across ubuntu-latest and
windows-latest. Windows is the platform this plugin exists for, so it is part
of the matrix rather than an afterthought. A second job checks that
package.json, plugin.json and marketplace.json carry the same version, which
is exactly the drift scripts/sync-version.js exists to prevent.
- release.yml triggers on a v*.*.* tag: builds and tests on both platforms,
refuses a tag that does not match package.json, creates the GitHub release
with generated notes, and publishes to npm with provenance. The npm step is
skipped rather than failed when no NPM_TOKEN secret is present, so a tag push
still produces a GitHub release on a repo without registry credentials.
- dependabot.yml keeps the pinned action SHAs and the dev dependencies current.
Third-party actions are pinned to commit SHAs with a version comment, workflow
permissions default to contents: read and are widened only in the publishing
job, and every multi-line script runs under set -euo pipefail. actionlint is
clean.
The test script changes from a `dist/test/*.test.js` glob to the `dist/test/`
directory: cmd.exe does not expand globs, so the glob form is unreliable on the
Windows runners the new matrix adds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Resolve test files explicitly instead of relying on shell or Node globbing
The CI matrix exposed that neither form of the test invocation is portable:
`node --test dist/test/*.test.js` relies on the shell expanding the glob, which
cmd.exe does not do, and `node --test dist/test` is accepted by Node 20 but not
by 22 or 24, which try to load the directory as a module and fail with
MODULE_NOT_FOUND.
scripts/run-tests.js lists dist/test/*.test.js via readdir and passes the files
to `node --test` directly, which behaves the same on every supported version and
on both platforms.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: .claude-plugin/marketplace.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -30,7 +30,7 @@
30
30
"name": "claude-code-win32",
31
31
"source": "./",
32
32
"description": "Makes Claude's Bash tool calls work cleanly on Windows: POSIX pass-through via Git Bash, PowerShell rewrites for common Unix idioms, MSYS drive-path translation, and deny-with-hint for Unix-only commands.",
Copy file name to clipboardExpand all lines: .claude-plugin/plugin.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
{
2
2
"name": "claude-code-win32",
3
-
"version": "0.1.1",
3
+
"version": "0.1.2",
4
4
"description": "Hybrid bash→PowerShell transpiler for Claude Code on Windows. Passes POSIX-safe commands through to Git Bash, rewrites Unix idioms to PowerShell equivalents, translates MSYS drive paths, and denies Unix-only commands with per-command hints.",
Copy file name to clipboardExpand all lines: README.md
+27-3Lines changed: 27 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ On macOS and Linux the hook checks `process.platform` first and no-ops immediate
10
10
## What You Get
11
11
12
12
-**No more "bash: command not found" on Windows** — Claude's `Bash` calls just work.
13
-
-**Zero-overhead fast path** — POSIX-safe commands (`git`, `npm`, `grep`, `sed`, …) stay in Git Bash; PowerShell startup is only paid when needed.
13
+
-**Zero-overhead fast path** — POSIX-safe commands (`git`, `npm`, `grep`, `sed`, `jq`, …) stay in Git Bash; PowerShell startup is only paid when needed. Unknown binaries stay there too — Git Bash sees more `PATH` than PowerShell does.
14
14
-**Targeted PS rewrites** — `xdg-open`, `pbcopy`, `uname`, `df`, `free`, and friends map to clean PowerShell equivalents.
15
15
-**MSYS drive-path translation** — `/c/Users/foo` → `C:/Users/foo` when the segment is routed to PS. URL-safe, packed-flag-aware (`-I/c/include` works), quote-aware.
16
16
-**Smart denial with hints** — `sudo`, `systemctl`, `apt`, `chmod`, etc. don't silently fail; Claude is told what to use instead (`Start-Process -Verb RunAs`, `Get-Service`, `winget`, `icacls`, …).
@@ -55,13 +55,13 @@ Registers a `PreToolUse` hook on the `Bash` tool. For each command:
55
55
2.**Shell detection** — probes for `pwsh` (PS 7+), falls back to `powershell.exe` (5.1). Cached 24h under `~/.claude/plugins/claude-code-win32.state.json`.
56
56
3.**Chain parse** — quote- and paren-aware split at `|`, `&&`, `||`, `;`, `&`. Redirect tokens (`2>&1`, `1>&2`, `&>file`, `&>>file`) are recognised and **not** misclassified as chain operators.
-**passthrough** — external `.exe` / POSIX allowlist (git, node, npm, docker, grep, sed, awk, jq, rg, …). **An unknown binary also passes through**: the allowlist can never be complete, and Git Bash resolves both the Windows `PATH` and the MSYS `/usr/bin` tree, while PowerShell only sees the former. Only PowerShell-shaped names (`Verb-Noun` cmdlets, `*.ps1`) are routed to PS.
59
59
-**rewrite** — known Unix idiom with a clean PowerShell equivalent (`xdg-open`, `pbcopy`, `whoami`, `uname`, `df`, `free`, …)
60
60
-**deny** — Unix-only with no Windows equivalent (package managers, `systemctl`, `sudo`, `chmod`, `dmesg`, `iptables`, …)
61
61
5.**Deny gates**:
62
62
- Any segment is Unix-only → deny with a per-command hint pointing Claude at the Windows-native alternative.
63
63
-`&&` / `||` present AND detected shell is PS 5.1 → deny with `https://aka.ms/powershell` install link.
64
-
6.**Pass fast-path** — all segments Git-Bash-safe, no rewrite needed, `preferPowerShell=false` → allow unchanged (avoids PS 100–500 ms startup).
64
+
6.**Pass fast-path** — all segments Git-Bash-safe (allowlisted or unknown), no rewrite needed, `preferPowerShell=false` → allow unchanged (avoids PS 100–500 ms startup).
65
65
7.**MSYS path translation** — for segments routed to PowerShell, leading `/c/`, `/d/`, … prefixes are rewritten to `C:/`, `D:/`, …. Boundary-aware: URLs (`https://…`), UNC paths (`//server/share`), `file:///c/…`, and single-quoted literals are left intact. Packed compiler flags like `-I/c/include`, `-Wl,/c/lib`, `-isystem/c/sys` are recognised and translated.
66
66
8.**Strategy A rewrite** — rebuild the chain in PowerShell syntax (`&&`/`||` native on PS 7; trailing `&` → `Start-Job`), prepend `$ProgressPreference='SilentlyContinue';` to suppress CLIXML progress noise on PS 5.1, wrap in `<exe>.exe -NoProfile -NonInteractive -EncodedCommand <base64>`.
67
67
@@ -86,8 +86,32 @@ npm run build
86
86
npm test
87
87
```
88
88
89
+
CI runs the suite on Node 20/22/24 across ubuntu-latest **and** windows-latest, and
90
+
checks that `package.json`, `.claude-plugin/plugin.json` and
91
+
`.claude-plugin/marketplace.json` carry the same version.
92
+
93
+
### Cutting a release
94
+
95
+
```bash
96
+
npm version <patch|minor|major># bumps package.json, syncs plugin/marketplace, tags, pushes
97
+
```
98
+
99
+
The `v*.*.*` tag triggers `.github/workflows/release.yml`: it builds and tests on
100
+
Linux and Windows, verifies the tag matches `package.json`, creates the GitHub
101
+
release with generated notes, and publishes to npm with provenance. The npm step is
102
+
skipped (not failed) when no `NPM_TOKEN` secret is configured.
103
+
89
104
## Status
90
105
91
106
**v0.1.0** — initial release. 40 unit tests, live verified on PS 5.1 (Windows 11).
92
107
93
108
Known limitation: nested commands under `xargs foo` are classified on the outer binary only. `sudo` is on the deny-list, so the most common case is already covered.
109
+
110
+
### Troubleshooting
111
+
112
+
**`jq: command not found` (or any tool that works in Git Bash but not through the hook).**
113
+
Fixed in v0.1.2. Before that, any binary missing from the POSIX allowlist pulled the
114
+
*entire* chain into PowerShell — including the `grep`/`sed`/`cat` segments around it.
115
+
PowerShell does not see the MSYS `/usr/bin` tree, so a perfectly installed `jq` reported
116
+
as missing. Unknown binaries now stay in Git Bash. If a tool is genuinely not installed,
117
+
install it on the Windows `PATH` (`winget install jqlang.jq`, `scoop install jq`).
0 commit comments