Skip to content

prefer-actions-exec-over-child-process: false negative on synchronous spawnSync used for output capture #58168

Description

@github-actions

Overview

prefer-actions-exec-over-child-process targets child_process calls used for output capture (exec, execSync, execFile, execFileSync) and deliberately excludes spawn/spawnSync, reasoning (per the rule's own source comments) that spawn-family APIs are for "long-running, detached, or interactively-streamed" processes that @actions/exec has no equivalent for.

That rationale doesn't hold for how spawnSync is actually used in this codebase: it's used synchronously, purely to capture git command output (stdout/stderr/status), in exactly the same shape as the execFileSync calls the rule already flags elsewhere. OUTPUT_CAPTURING_METHODS in the rule source is new Set(["exec", "execSync", "execFile", "execFileSync"])spawnSync is simply never checked, so these call sites are a silent false negative rather than an intentional exemption.

Grounded evidence

Live call sites (both inside github-script modules, both output-capturing)
  • actions/setup/js/git_helpers.cjs:131execGitSync():

    const result = spawnSync("git", args, {
      encoding: "utf8",
      maxBuffer: 100 * 1024 * 1024,
      timeout: defaultTimeoutMs,
      killSignal: "SIGKILL",
      ...spawnOptions,
      env: safeEnv,
    });
    // ...
    if (result.status !== 0) { /* throws using result.stderr */ }

    This function's own doc comment even says "Safely execute git command using spawnSync with args array" — it is functionally identical to the execFileSync pattern the rule already flags (synchronous, blocking, captures stdout/stderr/exit status, then throws on non-zero).

  • actions/setup/js/apply_samples.cjs:99-100:

    const { spawnSync } = require("child_process");
    const result = spawnSync("git", args, { cwd, encoding: "utf8", timeout: GIT_COMMAND_TIMEOUT_MS });

    Same pattern: synchronous git invocation for output capture, no streaming/detached/long-running behavior involved.

Both files carry the /// <reference types="@actions/github-script" /> marker the rule uses to gate on @actions/exec availability (isGitHubScriptModule), so both are squarely in-scope for the rule's intended domain — they're just missed because spawnSync isn't in the tracked method set.

Why this matters

The rule's exclusion of spawn/spawnSync is a blanket method-name exemption, not a behavior-based one. It doesn't distinguish "spawnSync used for long-running/streaming work" (a legitimate exemption) from "spawnSync used exactly like execFileSync, for one-shot synchronous output capture" (the rule's stated target). As written, anyone can dodge this diagnostic entirely just by picking spawnSync over execFileSync — same functionality, no lint signal.

Acceptance criteria

  • Extend detection to spawnSync (and spawn when its result is used synchronously / awaited in a way that captures output, if such a pattern exists) using the existing resolveChildProcessOutputMethodBinding/resolveChildProcessOutputMethod machinery, OR add a narrower heuristic: flag spawnSync calls whose result is used to read .stdout/.stderr/.status (i.e., genuinely used for output capture) while still exempting spawn/spawnSync invocations that only check .pid, attach .on(...) listeners, or otherwise indicate a long-running/streamed process.
  • Add test cases mirroring git_helpers.cjs's execGitSync shape (spawnSync + destructured result.stdout/result.status handling) as an "invalid" case, and a genuine long-running spawn(...) with event-listener usage (see trace_graders.cjs's sidecar spawn(process.execPath, [serverPath], {...})) as a "valid" (unflagged) case to lock in the boundary.
  • Re-run the rule statically (via Grep, since lint can't execute in this sandbox) against git_helpers.cjs:131 and apply_samples.cjs:99-100 to confirm both now report preferActionsExecSyncContext/preferActionsExec as appropriate.

References:

  • eslint-factory/src/rules/prefer-actions-exec-over-child-process.ts
  • actions/setup/js/git_helpers.cjs
  • actions/setup/js/apply_samples.cjs

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • api.anthropic.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.anthropic.com"

See Network Configuration for more information.

Generated by 🤖 ESLint Refiner · claude · agent · 351.8 AIC · ⌖ 5.05 AIC · ⊞ 5.8K ·

  • expires on Sep 9, 2026, 10:02 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

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions