fix(step): preserve cmd.exe quoting for {{files}} on Windows#824
Conversation
Rust's `Command::arg` applies MSVCRT-style argv escaping on Windows,
which collides with cmd.exe's own parsing rules. When a step command
contains already-cmd-quoted paths (from rendering `{{files}}` or
`{{workspace_files}}` via `ShellType::Cmd::quote`), the resulting
argv re-escaping causes cmd.exe to deliver tools arguments with
literal `"` characters embedded — splitting "file with spaces.txt"
into three separate args and turning "simple.txt" into `"simple.txt"`.
In practice this silently broke `{{files}}`-based checks on Windows:
tools like ruff and biome would exit 0 while processing zero files,
so hk would report success on malformed invocations.
Use the new `raw_arg` helper (with `new_direct` to skip the implicit
`cmd.exe /c` wrapping) so the rendered command string is appended
verbatim via `CommandExt::raw_arg`, letting cmd.exe parse its own
quoting without Rust's interference.
Fixes #823.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request addresses an issue on Windows where Rust's default argument escaping conflicts with cmd.exe quoting, particularly when using the {{files}} template. The changes introduce the use of raw_arg for the default cmd.exe runner to bypass MSVCRT-style re-escaping and include a new regression test. Feedback suggests extending this logic to custom shells that are identified as cmd.exe on Windows to ensure the fix is applied consistently.
| cmd = cmd.arg(arg); | ||
| } | ||
| cmd | ||
| cmd.arg(&run) |
There was a problem hiding this comment.
The fix for cmd.exe quoting should also be applied when a custom shell is provided, if that shell is detected as cmd.exe. Currently, the custom shell block still uses .arg(&run), which will trigger the same MSVCRT-style escaping issue that this PR aims to fix for the default shell path. By using raw_arg when the shell type is Cmd on Windows, we ensure that the rendered command string (which already contains cmd.exe-appropriate quoting from ShellType::Cmd::quote) is passed through verbatim.
| cmd.arg(&run) | |
| if cfg!(windows) && self.shell_type() == ShellType::Cmd { | |
| cmd.raw_arg(&run) | |
| } else { | |
| cmd.arg(&run) | |
| } |
Greptile SummaryFixes the Windows One unrelated file — Confidence Score: 5/5Safe to merge; the core Windows fix and test coverage are correct, with only a trivial stale lock file to clean up. All findings are P2. The
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Step::run] --> B{use_raw_cmd
cfg windows AND ShellType::Cmd}
B -- Yes --> C{self.shell set?}
B -- No --> D{self.shell set?}
C -- Yes --> E["new_direct(shell[0])
+ arg(shell[1..])
+ raw_arg(&run)"]
C -- No --> F["new_direct(cmd.exe)
+ arg(/c)
+ raw_arg(&run)"]
D -- Yes --> G["new(shell[0])
+ arg(shell[1..])
+ arg(&run)"]
D -- No --> H["new(sh)
+ arg(-o errexit -c)
+ arg(&run)"]
E --> I[cmd.execute]
F --> I
G --> I
H --> I
style E fill:#d4edda,stroke:#28a745
style F fill:#d4edda,stroke:#28a745
Reviews (5): Last reviewed commit: "test(windows): use file:// URI for pkl a..." | Re-trigger Greptile |
Extend the Windows cmd.exe quoting fix to steps that explicitly set
`shell = "cmd.exe"` in hk.pkl. Previously the custom-shell branch
still used `CmdLineRunner::new` + `.arg(&run)`, which re-applied
MSVCRT escaping even though `ShellType::Cmd::quote` had already
produced cmd-appropriate quoting.
Also tighten the Pester regression test with a positive assertion on
both filenames, so a future regression that silently expands
`{{files}}` to an empty list still fails the test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
b7d5a41 to
77de586
Compare
The `{{files}}` Pester test on Windows exits 101 in CI with only the
bare Pester assertion message surfaced. Normalize `$env:PKL_PATH`
to forward slashes for the pkl amends URI, write files as ASCII (no
BOM), and dump the hk output + hk.pkl + dir listing on failure so the
next CI run reveals the underlying error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
77de586 to
4a4f6e4
Compare
pkl's default `--allowed-modules` allowlist accepts `file:` URIs but rejects bare Windows absolute paths like `D:/a/hk/...`, so the amends `"D:/a/hk/hk/pkl/Config.pkl"` was causing hk to panic with a pkl allowlist error. Build an explicit `file:///` URI from `$env:PKL_PATH`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
76d7a90 to
6693338
Compare
### 🚀 Features
- add {{ hook_args }} template to pass to downstream commands by
[@JohanLorenzo](https://github.com/JohanLorenzo) in
[#807](#807)
### 🐛 Bug Fixes
- **(step)** preserve cmd.exe quoting for {{files}} on Windows by
[@jdx](https://github.com/jdx) in
[#824](#824)
### 🛡️ Security
- **(deps)** update dependency eslint to v10 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#813](#813)
### 📦️ Dependency Updates
- update taiki-e/upload-rust-binary-action digest to 10c1cf6 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#810](#810)
- update nick-fields/retry action to v4 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#812](#812)
- update anthropics/claude-code-action digest to 657fb7c by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#809](#809)
- update dependency typescript to v6 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#814](#814)
- update rust crate demand to v2 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#815](#815)
- update rust crate expr-lang to v1 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#816](#816)
- update rust crate similar to v3 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#817](#817)
- update rust crate toml to v1 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#820](#820)
- update actions/upload-artifact digest to 043fb46 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#821](#821)
- update anthropics/claude-code-action digest to b47fd72 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#822](#822)
### New Contributors
- @JohanLorenzo made their first contribution in
[#807](#807)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk release bookkeeping: version bumps, regenerated CLI/docs, and
routine dependency lockfile updates with no functional code changes in
this PR.
>
> **Overview**
> Bumps `hk` to **v1.42.0** and adds the corresponding `CHANGELOG.md`
release notes.
>
> Regenerates versioned docs/CLI artifacts (`docs/cli/*`,
`hk.usage.kdl`) and updates all documentation/examples to reference the
`v1.42.0` Pkl package URLs. Updates `Cargo.lock` for minor dependency
revisions.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
4b9834f. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Co-authored-by: mise-en-dev <123107610+mise-en-dev@users.noreply.github.com>
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [hk](https://github.com/jdx/hk) | minor | `1.41.1` → `1.42.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>jdx/hk (hk)</summary> ### [`v1.42.0`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1420---2026-04-12) [Compare Source](jdx/hk@v1.41.1...v1.42.0) ##### 🚀 Features - add {{ hook\_args }} template to pass to downstream commands by [@​JohanLorenzo](https://github.com/JohanLorenzo) in [#​807](jdx/hk#807) ##### 🐛 Bug Fixes - **(step)** preserve cmd.exe quoting for {{files}} on Windows by [@​jdx](https://github.com/jdx) in [#​824](jdx/hk#824) ##### 🛡️ Security - **(deps)** update dependency eslint to v10 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​813](jdx/hk#813) ##### 📦️ Dependency Updates - update taiki-e/upload-rust-binary-action digest to [`10c1cf6`](jdx/hk@10c1cf6) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​810](jdx/hk#810) - update nick-fields/retry action to v4 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​812](jdx/hk#812) - update anthropics/claude-code-action digest to [`657fb7c`](jdx/hk@657fb7c) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​809](jdx/hk#809) - update dependency typescript to v6 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​814](jdx/hk#814) - update rust crate demand to v2 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​815](jdx/hk#815) - update rust crate expr-lang to v1 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​816](jdx/hk#816) - update rust crate similar to v3 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​817](jdx/hk#817) - update rust crate toml to v1 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​820](jdx/hk#820) - update actions/upload-artifact digest to [`043fb46`](jdx/hk@043fb46) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​821](jdx/hk#821) - update anthropics/claude-code-action digest to [`b47fd72`](jdx/hk@b47fd72) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​822](jdx/hk#822) ##### New Contributors - [@​JohanLorenzo](https://github.com/JohanLorenzo) made their first contribution in [#​807](jdx/hk#807) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMTAuMTMiLCJ1cGRhdGVkSW5WZXIiOiI0My4xMTAuMTMiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbIlJlbm92YXRlIEJvdCIsImF1dG9tYXRpb246Ym90LWF1dGhvcmVkIiwiZGVwZW5kZW5jeS10eXBlOjptaW5vciJdfQ==-->
Fixes #823.
Summary
Command::argapplies MSVCRT-style argv escaping that collides withcmd.exe's own quoting, so a rendered{{files}}payload (already quoted viaShellType::Cmd::quote) reaches tools with literal"characters in the arguments. In practice this silently brokeruff,biome, and any other{{files}}-based check — they'd exit 0 while processing zero files, so hk would report success on malformed invocations.cmd.exe /cpath insrc/step/runner.rsto use a newCmdLineRunner::raw_arg(paired withnew_directto avoid the implicitcmd.exe /cwrapping), so the rendered command string is appended verbatim viaCommandExt::raw_argand cmd.exe can parse its own quoting without Rust interference. Also covers{{workspace_files}}, which goes through the same shell-quoting path.Test plan
cargo check(linux) passes.cargo check --target x86_64-pc-windows-gnupasses.{{files}} template passes clean arguments on Windows (no literal quotes)ine2e-win/check.Tests.ps1: materializes files includinghello world.txt, renderspython check_args.py {{files}}, and fails if any arg contains a literal"or references a missing path.ci-windowsjob in.github/workflows/ci.yml.🤖 Generated with Claude Code
Note
Medium Risk
Changes how step commands are executed on Windows
cmd.exeby switching to a raw-argument path; this can affect argument parsing/escaping and could alter behavior for existing Windows hooks.Overview
Fixes Windows
cmd.exeexecution so rendered{{files}}/{{workspace_files}}strings are passed verbatim to the shell (usingCmdLineRunner::new_direct+raw_arg) instead of being re-escaped by Rust, preventing literal quote characters from reaching tools and silently dropping files.Adds a Windows Pester regression test that validates
{{files}}expands to real paths (including spaces) without embedded quotes, and wires that test into theci-windowsGitHub Actions job.Reviewed by Cursor Bugbot for commit 6693338. Bugbot is set up for automated code reviews on this repo. Configure here.