From 94799bee6a82cc78a90d82eb65d560d552e457c4 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 28 Mar 2026 22:06:07 -0700 Subject: [PATCH] Fix DEP0190: remove all execFile+shell:true usage Replace execFile(cmd, args, {shell:true}) with exec(cmdString) in test-runner.ts and run.ts preflightTestRun. The shell:true+args pattern is deprecated (DEP0190) because args aren't escaped, making it equivalent to string concatenation. Since we validate commands via validateTestCommand before execution, using exec directly is both safer and more honest. Closes #135 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/commands/run.ts | 8 ++++---- src/scoring/test-runner.ts | 23 +++++++---------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/commands/run.ts b/src/commands/run.ts index 5863a73..2be4566 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -1,4 +1,4 @@ -import { execFile } from "node:child_process"; +import { exec as execCb, execFile } from "node:child_process"; import { mkdir, readFile, statfs, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -322,14 +322,14 @@ export async function retry(opts: RunOptions): Promise { * Returns a warning string if the tests fail, or null if they pass. */ export async function preflightTestRun(testCmd: string, repoRoot: string): Promise { - const { cmd, args } = parseTestCommand(testCmd); + const { cmd } = parseTestCommand(testCmd); if (!cmd) return null; + const execAsync = promisify(execCb); try { - await execFileAsync(cmd, args, { + await execAsync(testCmd, { cwd: repoRoot, timeout: 60_000, - shell: true, env: { ...process.env, CI: "true" }, }); return null; diff --git a/src/scoring/test-runner.ts b/src/scoring/test-runner.ts index 83c158f..705c867 100644 --- a/src/scoring/test-runner.ts +++ b/src/scoring/test-runner.ts @@ -1,10 +1,10 @@ -import { execFile } from "node:child_process"; +import { exec as execCb } from "node:child_process"; import { access } from "node:fs/promises"; import { join } from "node:path"; import { promisify } from "node:util"; import type { TestResult } from "../types.js"; -const exec = promisify(execFile); +const exec = promisify(execCb); const DEFAULT_TEST_TIMEOUT_MS = 120_000; @@ -65,7 +65,7 @@ export async function runTests( }; } - const { cmd, args } = parseTestCommand(testCmd); + const { cmd } = parseTestCommand(testCmd); if (!cmd) { return { @@ -88,12 +88,12 @@ export async function runTests( } try { - // Use execFile with shell:true for cross-platform command resolution - // while keeping args as an array to prevent injection via arguments. - const { stdout, stderr } = await exec(cmd, args, { + // Use exec (shell string) for cross-platform command resolution (npx, npm, etc.). + // Safety: testCmd is validated by validateTestCommand() which rejects shell operators. + // This avoids the DEP0190 deprecation from execFile + shell:true + args array. + const { stdout, stderr } = await exec(testCmd, { cwd: worktreePath, timeout: timeoutMs, - shell: true, env: { ...process.env, CI: "true" }, }); return { @@ -120,15 +120,6 @@ export async function runTests( }; } - if (typeof e.code === "string" && e.code === "ENOENT") { - return { - agentId, - passed: false, - output: `Command not found: ${cmd}. Is it installed?`, - exitCode: 127, - }; - } - return { agentId, passed: false,