Skip to content

eslint-factory: prefer-actions-exec-over-child-process misses promisify()-wrapped child_process methods #55664

Description

@github-actions

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

  • const execAsync = promisify(exec); await execAsync(...) (destructured exec from require("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.
  • New test cases added to prefer-actions-exec-over-child-process.test.ts covering both binding shapes above.
  • validate_secrets.cjs:252 is 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 live actions/setup/js corpus.

Generated by 🤖 ESLint Refiner · claude · agent · 223.9 AIC · ⌖ 7.27 AIC · ⊞ 5.8K · ◷

  • expires on Aug 31, 2026, 9:32 PM UTC-08:00

Activity

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions