Skip to content

fix: support .cmd/.bat shims on Windows in exec() (#446)#469

Open
nikos1005 wants to merge 3 commits into
profullstack:masterfrom
nikos1005:fix/windows-cmd-shims
Open

fix: support .cmd/.bat shims on Windows in exec() (#446)#469
nikos1005 wants to merge 3 commits into
profullstack:masterfrom
nikos1005:fix/windows-cmd-shims

Conversation

@nikos1005
Copy link
Copy Markdown

Summary

Detect .cmd/.bat extensions on Windows and spawn via cmd.exe with shell:true.

Fixes #446

Arsen Bekirov added 3 commits May 29, 2026 18:52
…profullstack#454)

Replace destructuring assignment with validation to avoid TS18048 under noUncheckedIndexedAccess.
…llstack#452)

Use ../../../../tsconfig.base.json instead of ../../tsconfig.base.json which incorrectly pointed to packages/tsconfig.base.json.
…k#446)

Detect .cmd/.bat extensions on Windows and spawn via cmd.exe with shell:true.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 29, 2026

Greptile Summary

This PR fixes Windows .cmd/.bat shim execution in exec() by detecting those extensions and routing through cmd.exe, and also includes tsconfig extend-path fixes for the bot packages and improved recordId parsing in the Google Cloud DNS provider.

  • packages/core/src/exec.ts: Detects .cmd/.bat on Windows and spawns via cmd.exe /d /s /c, but also sets shell: true, which causes Node.js to double-wrap the invocation through cmd.exe; one of the two approaches should be removed.
  • tsconfig files (bot/core, discord, signal, telegram, whatsapp): Corrects the extends path from ../../tsconfig.base.json to ../../../tsconfig.base.json to match the actual three-level depth of these packages.
  • packages/dns/googledns/src/index.ts: Improves recordId splitting to handle FQDNs that contain / characters, and adds a validation guard with a clear error message.

Confidence Score: 3/5

The core exec() change has a logic error that will cause every .cmd/.bat invocation on Windows to go through cmd.exe twice, which may silently produce wrong exit codes or broken argument quoting. The tsconfig and DNS changes are safe.

The Windows shim path in exec.ts both manually sets the executable to 'cmd' with /d /s /c args and also enables shell:true, which instructs Node.js to wrap the command in cmd.exe a second time. This double-wrapping is the primary change introduced by this PR and affects every .cmd/.bat call on Windows.

packages/core/src/exec.ts needs the most attention — the simultaneous use of manual cmd.exe invocation and shell:true should be resolved before merging.

Important Files Changed

Filename Overview
packages/core/src/exec.ts Adds Windows .cmd/.bat shim detection, but simultaneously manually invokes cmd.exe and sets shell:true, causing double-wrapping; also missing case-insensitive extension check.
packages/dns/googledns/src/index.ts Improves recordId parsing to handle FQDNs containing slashes and adds input validation; introduces a redundant null guard that is dead code after the existing length check.
packages/bot/core/tsconfig.json Corrects the tsconfig.base.json extend path from ../../ to ../../../, matching the actual depth of packages/bot/core from the repo root.
packages/bot/discord/tsconfig.json Same tsconfig extend-path correction as packages/bot/core — no issues.
packages/bot/signal/tsconfig.json Same tsconfig extend-path correction — no issues.
packages/bot/telegram/tsconfig.json Same tsconfig extend-path correction — no issues.
packages/bot/whatsapp/tsconfig.json Same tsconfig extend-path correction — no issues.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["exec(cmd, args, opts)"] --> B{isWin && cmd ends with .cmd/.bat?}
    B -- No --> C["spawn(cmd, args, opts)"]
    B -- Yes --> D["spawn('cmd', ['/d','/s','/c', cmd, ...args], {shell:true})"]
    D --> E["Node.js shell:true wraps again\ncmd.exe /d /s /c cmd /d /s /c foo.cmd args"]
    E --> F["⚠️ Double cmd.exe invocation"]
    C --> G["Process runs normally"]

    style F fill:#ffcccc,stroke:#cc0000
    style G fill:#ccffcc,stroke:#009900
Loading

Reviews (1): Last reviewed commit: "fix: support .cmd/.bat shims on Windows ..." | Re-trigger Greptile

Comment thread packages/core/src/exec.ts
Comment on lines +34 to 39
const child = spawn(needsShell ? 'cmd' : cmd, needsShell ? ['/d', '/s', '/c', cmd, ...args] : args, {
cwd: opts.cwd,
env: { ...process.env, ...extraEnv },
stdio: ['ignore', 'pipe', 'pipe'],
...(needsShell ? { shell: true } : {}),
});
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.

P1 Double-wrapping through cmd.exe

When needsShell is true, the code manually sets the executable to 'cmd' and prepends /d /s /c to the args, but it also passes shell: true. On Windows, shell: true causes Node.js to wrap the command as cmd.exe /d /s /c "<exe> <args>" — so the actual invocation becomes cmd.exe /d /s /c cmd /d /s /c foo.cmd <args>, spawning cmd.exe twice. Choose one approach: either manually invoke cmd without shell: true, or keep shell: true and just pass the original cmd/args.

Suggested change
const child = spawn(needsShell ? 'cmd' : cmd, needsShell ? ['/d', '/s', '/c', cmd, ...args] : args, {
cwd: opts.cwd,
env: { ...process.env, ...extraEnv },
stdio: ['ignore', 'pipe', 'pipe'],
...(needsShell ? { shell: true } : {}),
});
const child = spawn(needsShell ? 'cmd' : cmd, needsShell ? ['/d', '/s', '/c', cmd, ...args] : args, {
cwd: opts.cwd,
env: { ...process.env, ...extraEnv },
stdio: ['ignore', 'pipe', 'pipe'],
});

Comment thread packages/core/src/exec.ts
const child = spawn(cmd, args, {
// On Windows, .cmd/.bat shims must be spawned with shell:true or via cmd.exe
const isWin = process.platform === 'win32';
const needsShell = isWin && (cmd.endsWith('.cmd') || cmd.endsWith('.bat'));
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.

P2 Case-insensitive extension matching needed on Windows

Windows file systems are case-insensitive, so shims named foo.CMD or foo.BAT (uppercase) will not be detected, and spawning them without the shell wrapper will fail with ENOENT. Lowercasing before the check avoids this edge case.

Suggested change
const needsShell = isWin && (cmd.endsWith('.cmd') || cmd.endsWith('.bat'));
const needsShell = isWin && (cmd.toLowerCase().endsWith('.cmd') || cmd.toLowerCase().endsWith('.bat'));

Comment on lines 134 to +136
if (existing.length === 0) return;
const first = existing[0];
if (!first) return;
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.

P2 Redundant null guard after a length check

existing.length === 0 is checked on the line immediately above, so when execution reaches const first = existing[0], the array is guaranteed to be non-empty and first can never be undefined. The if (!first) return; guard is dead code and can be removed.

Suggested change
if (existing.length === 0) return;
const first = existing[0];
if (!first) return;
if (existing.length === 0) return;
const first = existing[0];

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.

Shared exec helper cannot launch Windows .cmd CLI shims

1 participant