prefer-actions-exec-over-child-process: false positive on shim.cjs dual-mode standalone/github-script files
- Dominant language
- Go
- Stars
- 5.1k
- Forks
- 541
- Avg merge
- 5h 48m
- Merged PRs (30d)
- 773
Description
### Summary
`prefer-actions-exec-over-child-process` flags `child_process` output-capturing calls (`exec`/`execSync`/`execFile`/`execFileSync`) whenever the containing file carries the `/// ` marker, on the theory that any such file always runs inside the `github-script` action step where `@actions/exec`'s `exec()` global is available. That assumption is false for files that also `require("./shim.cjs")` — `shim.cjs` exists specifically to let github-script-flavored modules run standalone (e.g. inside the safe-outputs and mcp-scripts MCP servers), and its own docstring says so explicitly. Crucially, `shim.cjs` only polyfills `core`/`context` — it does **not** polyfill `exec`, so in standalone mode the suggested `@actions/exec` rewrite is not actually available, making the diagnostic a false positive for these files.
### Live evidence
**`actions/setup/js/merge_remote_agent_github_folder.cjs`** — the file's own header docstring (lines 11-13) states:
> "This script runs in a github-script context where core object is available globally. When run as a plain Node.js process, shim.cjs provides global.core."
`require("./shim.cjs")` at line 26. Flagged `execFileSync("git", [...])` calls at lines 194, 198, 207, 212, 215 (all inside `sparseCheckoutGithubFolder`-style helpers) — 5 call sites.
**`actions/setup/js/build_checkout_manifest.cjs`** — carries the marker (line 2) and `require("./shim.cjs")` at line 4. Flagged `execFileSync("git", ...)` (line 52) and `execFileSync("gh", ...)` (line 61) inside `resolveDefaultBranch()` — 2 call sites.
**`actions/setup/js/shim.cjs`** (94 lines) confirms the mechanism: its docstring says it "Provides minimal `global.core` and `global.context` shims so that modules written for the GitHub Actions `github-script` context... work correctly when executed as plain Node.js processes, such as inside the safe-outputs and mcp-scripts MCP servers." It only polyfills `core.{debug,info,notice,warning,error,setFailed,setOutput,setSecret}` and `context.{eventName,sha,ref,...}` — there is no `exec`/`io`/`github`/`getOctokit` shim at all.
Contrast with `actions/setup/js/get_current_branch.cjs`, which also carries the marker and calls `execSync` (line 21) but does **not** require `shim.cjs` — that file is correctly flagged as a true positive, since it has no standalone-execution fallback path.
### Root cause
`eslint-factory/src/rules/prefer-actions-exec-over-child-process.ts` gates entirely on `isGitHubScriptModule(sourceCode)`, which only checks for the triple-slash reference comment:
```ts
const GITHUB_SCRIPT_REFERENCE_PATTERN = //;
```
It never checks whether the file also `require("./shim.cjs")` — which is the codebase's own signal that a file is designed to run in both github-script and standalone-Node contexts, and therefore cannot assume `@actions/exec`'s `exec` global is present.
### Suggested fix
Add a `requiresShimCjs(sourceCode)` check (grep the file's `require(...)` calls for a literal `"./shim.cjs"` argument) alongside `isGitHubScriptModule`. When a file both carries the marker and requires `shim.cjs`, either:
- skip reporting entirely (treat as a standalone-capable module, same as files lacking the marker), or
- adjust the diagnostic message to note the dual-mode caveat instead of unconditionally suggesting `@actions/exec`.
### Acceptance criteria
- [ ] `build_checkout_manifest.cjs` (lines 52, 61) and `merge_remote_agent_github_folder.cjs` (lines 194, 198, 207, 212, 215) no longer produce `prefer-actions-exec-over-child-process` diagnostics, OR their diagnostic is adjusted to acknowledge the standalone-execution caveat.
- [ ] `get_current_branch.cjs:21` (marker present, no `shim.cjs` require) continues to be flagged — the fix must not blanket-suppress all marker-gated files.
- [ ] A test case is added covering a github-script-marked file that also requires `shim.cjs`, asserting no false-positive diagnostic (or the adjusted message) is produced.
Filed by the automated ESLint Refiner workflow. This issue will auto-close if left unaddressed for about a week — that expiry is not equivalent to rejection; re-check the live rule/corpus before assuming it's resolved.
> [!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:
>
> ```yaml
> network:
> allowed:
> - defaults
> - "api.anthropic.com"
> ```
>
> See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information.
>
>
> Generated by [🤖 ESLint Refiner](https://github.com/github/gh-aw/actions/runs/34932497227) · claude · agent · 406 AIC · ⌖ 7.86 AIC · ⊞ 5.8K · [◷](https://github.com/search?q=repo%3Agithub%2Fgh-aw+is%3Aissue+%22gh-aw-workflow-call-id%3A+github%2Fgh-aw%2Feslint-refiner%22&type=issues)
> - [x] expires on Sep 21, 2026, 9:40 PM UTC-08:00
Contributor guide
Research direction
Start in eslint-factory/src/rules/prefer-actions-exec-over-child-process.ts and inspect isGitHubScriptModule alongside the ./shim.cjs usage described for actions/setup/js/shim.cjs. Add rule-test coverage for the dual-mode case while preserving the diagnostic for actions/setup/js/get_current_branch.cjs. Done means the two shim-using files no longer produce the false positive, while the non-shim file remains flagged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- eslint, github-actions, javascript, node.js, typescript
- Domain
- devtools, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100