Skip to content

fix: resolve $ template argv on Windows instead of a shell string - #89

Open
markmybytes wants to merge 1 commit into
iHildy:mainfrom
markmybytes:fix/windows-shell-argv
Open

markmybytes wants to merge 1 commit into
iHildy:mainfrom
markmybytes:fix/windows-shell-argv

Conversation

@markmybytes

@markmybytes markmybytes commented Oct 13, 2026 •

Copy link
Copy Markdown

Summary

Fixes #88.

Since the v2 plugin entrypoint (#81), every git/gh command from the plugin runs through the Node shell shim in src/shell-node.ts. That shim interpolated values with POSIX single-quote escaping ('...') and executed the result via child_process.exec — which on Windows spawns cmd.exe. cmd.exe does not treat single quotes as argument delimiters, so gh/git received quotes literally:

Unable to verify repo visibility: Command failed: gh repo view '<owner>/<repo>' --json isPrivate
GraphQL: Could not resolve to a Repository with the name ''<owner>/<repo>''. (repository)

This broke pull, push, and /sync-link on Windows for any config that reaches ensureRepoPrivate() (includeSecrets / includeMcpSecrets / includeSessions / includePromptStash). v0.10.1 was unaffected because the v1 entrypoint used Bun's $ template tag, which does its own Windows argv handling.

Fix

Drop the shell instead of trying to quote for it — the approach the issue calls preferred, since platform-aware quoting keeps discipline exposed to cmd.exe metacharacters:

  • buildArgv tokenizes the $-tagged template into an argv array with Bun $ semantics: static text splits on whitespace, each ${} glues into the current token as one argument (origin/${branch}..HEAD stays one element), and an empty value keeps its argv slot (git commit -m ${''} does not collapse to a bare -m, which would hang in an interactive editor).
  • The template now spawns via execFile with no shell, so no platform quoting rules apply at all.
  • The shell fallback only remains for cases Node cannot spawn shell-less: .cmd/.bat shims (refused since CVE-2024-27980) and bare PATHEXT-resolved names (ENOENT). Those re-run as a one-liner through exec, platform-quoted — MSVCRT list2cmdline rules (quoteWin32) on Windows, POSIX single-quote on the rest. The fallback is documented as re-entering a shell, so its PATH resolution and %…% expansion ceiling differ from a direct spawn.
  • Because there is no shell anymore, &&, pipes, redirects, and globs in template text now arrive as literal argv elements rather than operators. No existing call site uses any of them (verified by scan); the behavior difference is documented in the shim's header for future call sites.

child_process.exec defaults to cmd.exe on Windows, which treats POSIX
single quotes as literal characters. Every interpolated git call
(git -C <path> ...) passed quote characters to git and failed with
fatal: cannot change to ''<path>'', breaking pull, push, status, and
commit on Windows entirely.

Run the tokenized command via execFile with no shell. Static text
splits on whitespace; interpolated values glue to adjacent text so
mid-token interpolations (origin/${branch}..HEAD, --max-count=${n})
stay a single argv element, and empty values keep their argv slot so
'commit -m ""' cannot collapse to a bare -m and hang in an editor.
Fall back to a quoted shell one-liner only when the executable is a
.cmd/.bat shim (EINVAL since CVE-2024-27980); that path retains the
cmd.exe percent-expansion and newline ceilings.

All 37 git/gh/op call-site templates use no shell features, so this
is a drop-in replacement that also removes command-injection surface
for commit messages and repo paths.
@markmybytes
markmybytes force-pushed the fix/windows-shell-argv branch from 0920374 to 2ce387e Compare October 13, 2026 04:14
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.

Sync failed on Windows

1 participant