Summary
prefer-actions-exec-over-child-process resolves output-capturing/handle-returning child_process methods via resolveChildProcessOutputMethodBinding() / resolveChildProcessOutputMethod(), but neither function traces through a promisify() wrapper. A call site that does const execAsync = promisify(exec); await execAsync(...) is never recognized as a child_process call and the rule silently passes — even though it's the exact output-capturing anti-pattern (@actions/exec.getExecOutput should be preferred) the rule exists to catch.
Grounding
actions/setup/js/validate_secrets.cjs:16-19:
const { promisify } = require("util");
const { exec } = require("child_process");
const execAsync = promisify(exec);
and validate_secrets.cjs:252 (inside testCopilotCLI, which has the required /// <reference types="@actions/github-script" /> marker at line 2, so it's in-scope per isGitHubScriptModule()):
const { stdout, stderr } = await execAsync('which copilot 2>/dev/null || echo ""');
This is a genuine, live output-capturing child_process.exec invocation the rule is designed to flag, but it's a false negative because execAsync is bound to promisify(exec), not directly to exec/require("child_process").exec.
Confirmed via eslint-factory/src/rules/prefer-actions-exec-over-child-process.test.ts: no test exercises a promisify(...)-wrapped binding.
Proposed fix
Extend the binding resolver so that when an identifier is initialized as promisify(<child_process method reference>), the resolver still recognizes calls through that identifier as the underlying child_process method (for both OUTPUT_CAPTURING_METHODS and HANDLE_RETURNING_METHODS).
Acceptance criteria
Methodology note
Found via static grounding: no live lint run was available in this environment (npm firewalled, no node_modules/dist). This finding was verified by reading the rule's resolver logic in eslint-factory/src/rules/prefer-actions-exec-over-child-process.ts, cross-referencing its test suite for coverage gaps, and confirming a genuine unflagged call site in the live actions/setup/js corpus.
Generated by 🤖 ESLint Refiner · claude · agent · 223.9 AIC · ⌖ 7.27 AIC · ⊞ 5.8K · ◷
Summary
prefer-actions-exec-over-child-processresolves output-capturing/handle-returningchild_processmethods viaresolveChildProcessOutputMethodBinding()/resolveChildProcessOutputMethod(), but neither function traces through apromisify()wrapper. A call site that doesconst execAsync = promisify(exec); await execAsync(...)is never recognized as achild_processcall and the rule silently passes — even though it's the exact output-capturing anti-pattern (@actions/exec.getExecOutputshould be preferred) the rule exists to catch.Grounding
actions/setup/js/validate_secrets.cjs:16-19:and
validate_secrets.cjs:252(insidetestCopilotCLI, which has the required/// <reference types="@actions/github-script" />marker at line 2, so it's in-scope perisGitHubScriptModule()):This is a genuine, live output-capturing
child_process.execinvocation the rule is designed to flag, but it's a false negative becauseexecAsyncis bound topromisify(exec), not directly toexec/require("child_process").exec.Confirmed via
eslint-factory/src/rules/prefer-actions-exec-over-child-process.test.ts: no test exercises apromisify(...)-wrapped binding.Proposed fix
Extend the binding resolver so that when an identifier is initialized as
promisify(<child_process method reference>), the resolver still recognizes calls through that identifier as the underlyingchild_processmethod (for bothOUTPUT_CAPTURING_METHODSandHANDLE_RETURNING_METHODS).Acceptance criteria
const execAsync = promisify(exec); await execAsync(...)(destructuredexecfromrequire("child_process")) is flagged.const execAsync = promisify(require("child_process").exec);(direct member access, no destructure) is flagged.promisify()-wrapped non-output-capturing methods that aren't in scope for this rule are not newly over-flagged.prefer-actions-exec-over-child-process.test.tscovering both binding shapes above.validate_secrets.cjs:252is flagged after the fix (manually verified against the live file, since lint cannot currently be run in this sandbox).Methodology note
Found via static grounding: no live lint run was available in this environment (npm firewalled, no node_modules/dist). This finding was verified by reading the rule's resolver logic in
eslint-factory/src/rules/prefer-actions-exec-over-child-process.ts, cross-referencing its test suite for coverage gaps, and confirming a genuine unflagged call site in the liveactions/setup/jscorpus.