Skip to content

[Bug]: Windows DEP0190 from resolveSpawnCommand spawn(args, { shell: true }) for .cmd/.bat shims #12797

Description

@synephi

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Related: #2537 (cmd.exe flashes from OpenCode shell: true + taskkill). This report is the Node 24 DEP0190 path in the shared spawn helper, which is still present on Nightly 0.0.43-nightly.20260920.2005.

Area

packages/contracts or packages/shared

Steps to reproduce

  1. Install T3 Code Nightly on Windows 11 with Node 24 (Nightly embeds Node; system Node here is v24.13.0).
  2. Enable the OpenCode provider with the default binaryPath of opencode (the npm .cmd shim under %APPDATA%\npm\opencode.cmd).
  3. Start T3 Code Desktop. Leave Windows Terminal as the default console host.
  4. Watch the desktop, or read %USERPROFILE%\.t3\userdata\logs\server-child.log.

Expected behavior

No Node deprecation warning, and no stray cmd.exe console tab, when T3 launches provider CLIs.

Windows .cmd / .bat shims should be started as a single cmd.exe /d /s /c "<escaped command line>" with shell: false and windowsHide: true (or by pointing at the real .exe).

Actual behavior

A cmd.exe window / Windows Terminal tab appears with:

(node:<pid>) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.
(Use `t3 --trace-deprecation ...` to show where the warning was created)

Desktop backend stderr uses the Electron argv0, so the helper line is Use T3 Code (Nightly) --trace-deprecation .... The CLI binary prints Use t3 --trace-deprecation .... Same warning.

Process list on this machine while Nightly was running:

cmd.exe /d /s /c ""C:\Users\<user>\AppData\Roaming\npm\opencode.CMD" "serve" "--hostname=127.0.0.1" "--port=<ephemeral>""
parent: T3 Code (Nightly).exe ... resources\server.asar\apps\server\dist\bin.mjs --bootstrap-fd 3

That is Node's spawn(file, args, { shell: true }) expansion.

Impact

Cosmetic issue

It does not break chats, but it repeats on every backend start and on every .cmd provider spawn. Node documents DEP0190 as the insecure concatenate-args path; T3 already escapes args itself, so the warning is noisy rather than an active injection hole.

Version or commit

T3 Code (Nightly) 0.0.43-nightly.20260920.2005

Environment

Windows 11, T3 Code Nightly desktop, Node v24.13.0. OpenCode enabled via %APPDATA%\npm\opencode.cmd → opencode-ai\bin\opencode.exe. Windows Terminal is the default console host (HKCU\Console\%%Startup DelegationTerminal {E12CFF52-A866-4C77-9A90-F570A7AA2C6B}).

Logs or stack traces

From ~/.t3/userdata/logs/server-child.log (every backend start):

{"message":"backend child process output","level":"ERROR","annotations":{"component":"desktop-backend-child","stream":"stderr","text":"(node:86272) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.\n(Use `T3 Code (Nightly) --trace-deprecation ...` to show where the warning was created)\n"}}

Source still in Nightly server.asar / current packages/shared/src/shell.ts resolveSpawnCommand:

if (extension !== ".cmd" && extension !== ".bat") {
  return { command: resolvedCommand, args: [...args], shell: false };
}

return {
  command: escapeWindowsShellArg(resolvedCommand),
  args: sanitizeShellModeArgsForPlatform(args, platform),
  shell: true,
};

The comments next to escapeWindowsShellArg already describe Node joining args with spaces and handing them to cmd.exe unquoted — that is exactly DEP0190.

There is also cp.spawnSync('npm.cmd', ['view', ...], { shell: true }) in the asar for version lookup.

Workaround

Point provider binaryPath at the real .exe so resolveSpawnCommand takes the shell: false branch. For OpenCode:

%APPDATA%\npm\node_modules\opencode-ai\bin\opencode.exe

That avoids the npm .cmd shim for that one provider. It does not fix npm.cmd or other shims.

Suggested fix

Keep the existing escapeWindowsShellArg / sanitizeShellModeArgsForPlatform quoting, but stop passing shell: true together with an args array.

When the resolved path is .cmd / .bat, spawn ComSpec directly:

return {
  command: process.env.ComSpec || "cmd.exe",
  args: ["/d", "/s", "/c", buildCmdExeCommandLine(resolvedCommand, args)],
  shell: false,
  // plus windowsHide: true / windowsVerbatimArguments: true at the spawn site
};

Same pattern OpenClaw used for DEP0190 on Windows .cmd runners. .exe / .com paths can stay shell: false as they do today.

Activity

  1. juliusmarminge commented on Sep 20, 2026

    @juliusmarminge
    Member

    Triage

    Confirmed on current main. This is a real Windows bug in the shared spawn helper, not a duplicate of #2537.

    Diagnosis

    packages/shared/src/shell.ts resolveSpawnCommand still does this for .cmd / .bat:

    return {
      command: escapeWindowsShellArg(resolvedCommand),
      args: sanitizeShellModeArgsForPlatform(args, platform),
      shell: true,
    };

    That is exactly Node’s DEP0190 path: spawn(file, args, { shell: true }) concatenates args and hands them to %ComSpec% /d /s /c …. Nightly embeds Node 24, so every such spawn prints:

    [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.
    

    Desktop logs that as desktop-backend-child stderr (backend child process output). The captured line

    cmd.exe /d /s /c ""…\npm\opencode.CMD" "serve" "--hostname=127.0.0.1" "--port=…"
    

    matches OpenCode’s startOpenCodeServerProcess going through this helper (apps/server/src/provider/opencodeRuntime.ts). Same helper is used for Codex/Claude/Cursor/Grok probes, processRunner, ACP, and npm → npm.cmd provider updates.

    T3 already escapes each arg (escapeWindowsShellArg documents Node’s unquoted join). This is noisy / a visible cmd.exe or Windows Terminal tab, not an active injection hole. Chats still work.

    .exe / .com already take the shell: false branch, which is why pointing OpenCode binaryPath at opencode-ai\bin\opencode.exe works around it for that one provider.

    I did not find cp.spawnSync('npm.cmd', ['view', …], { shell: true }) in current source. Latest-version lookup is HTTP (fetchNpmLatestVersion). npm.cmd still goes through this helper on updates.

    Not a duplicate of #2537

    #2537 is Effect taskkill / missing hide on kill, plus idle git/gh/conhost churn. This issue is T3’s own shell: true + args for npm shims. Effect already defaults windowsHide when not detached (@effect/platform-node-shared 4.0.0-rc.115). Bumping Effect or hiding taskkill will not remove DEP0190. Keep both open; they share a Windows console symptom only.

    Related, not the same: #10818 (closed dup of #2537), #12498 (idle churn / WMI), #12600 (PATH-scan cache on this helper — does not change shell: true).

    Suggested fix

    Keep the existing quoting. Stop passing shell: true together with an args array. For .cmd / .bat, spawn ComSpec directly:

    return {
      command: process.env.ComSpec || "cmd.exe",
      args: ["/d", "/s", "/c", buildCmdExeCommandLine(resolvedCommand, args)],
      shell: false,
    };

    Callers already pass shell: spawnCommand.shell, so the helper is the whole fix. Tests that currently assert shell: true:

    • packages/shared/src/shell.test.ts (escapes the executable and arguments for Windows command shims)
    • apps/server/src/provider/providerMaintenanceRunner.test.ts (resolves npm to a .cmd shim and routes through the shell on win32)

    Claude’s SDK path already unwraps .cmd → real .exe (ClaudeExecutable.ts) because the SDK cannot use shell: true. Same idea for other providers is optional; the ComSpec spawn is the general fix.

    Workaround

    Set the provider binaryPath to the real .exe (for OpenCode: %APPDATA%\npm\node_modules\opencode-ai\bin\opencode.exe). That does not fix npm.cmd or other shims.

    Severity

    Cosmetic / low. Repeats on every backend start and every .cmd provider spawn. Actionable locally.

    Leaving this open as an accepted bug. #2537 stays on the Effect / idle-churn track.

  2. added
    acceptedfeature request accepted
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Sep 20, 2026
  3. cestercian commented on Sep 21, 2026

    @cestercian
    Contributor

    I'd like to take this — working on a fix.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions