Skip to content

feat(eslint-factory): add require-execfilesync-try-catch rule - #47251

Merged
pelikhan merged 8 commits into
mainfrom
copilot/eslint-factory-cover-execfilesync
Jul 22, 2026
Merged

feat(eslint-factory): add require-execfilesync-try-catch rule#47251
pelikhan merged 8 commits into
mainfrom
copilot/eslint-factory-cover-execfilesync

Conversation

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

execFileSync has identical throw-on-failure semantics to execSync (throws ChildProcessError on non-zero exit or signal), but was not covered by any lint rule — leaving unprotected call sites able to crash actions silently.

Changes

  • New rule require-execfilesync-try-catch — flags unprotected execFileSync calls sourced from child_process/node:child_process; emits a suggestion that wraps the enclosing statement in try/catch with a typed rethrow
  • All binding forms covered — destructured, namespace (cp.execFileSync), aliased (const run = cp.execFileSync), ESM import
  • Registered and enabled — added to src/index.ts and enabled as "warn" in eslint.config.cjs
  • Docs — README table entry and section for the new rule, plus a missing entry for require-execsync-try-catch; explicitly documents that execFile (async, callback-based) is out of scope
// ✗ flagged
const { execFileSync } = require("child_process");
execFileSync("git", ["fetch"]);

// ✓ passes
try {
  execFileSync("git", ["fetch"]);
} catch (err) {
  throw new Error("execFileSync failed: " + (err instanceof Error ? err.message : String(err)), { cause: err });
}

Generated by 👨‍🍳 PR Sous Chef · gpt54 9.56 AIC · ⌖ 8.4 AIC · ⊞ 7K ·
Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · gpt54 14.4 AIC · ⌖ 7.63 AIC · ⊞ 7K ·
Comment /souschef to run again

Cover execFileSync with the same try/catch requirement as execSync.
Both methods have identical throw-on-failure semantics (ChildProcessError
on non-zero exit or signal termination).

Changes:
- Add require-execfilesync-try-catch.ts rule (mirrors require-execsync-try-catch)
- Add require-execfilesync-try-catch.test.ts with 8 test cases
- Register new rule in src/index.ts
- Enable rule as "warn" in eslint.config.cjs
- Add table entry and docs section for both execSync and execFileSync rules in README
  (require-execsync-try-catch was previously undocumented)
- Note that execFile (async) is intentionally out of scope

Closes #47211

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Add rule to cover execFileSync in require-execsync-try-catch feat(eslint-factory): add require-execfilesync-try-catch rule Jul 22, 2026
Copilot AI requested a review from pelikhan July 22, 2026 09:19
@pelikhan
pelikhan marked this pull request as ready for review July 22, 2026 09:25
Copilot AI review requested due to automatic review settings July 22, 2026 09:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds lint coverage requiring child_process.execFileSync calls to use protective try/catch.

Changes:

  • Adds and registers the new rule.
  • Covers CommonJS, ESM, namespace, and alias bindings.
  • Adds tests, configuration, and documentation.
Show a summary per file
File Description
require-execfilesync-try-catch.ts Implements detection and suggestions.
require-execfilesync-try-catch.test.ts Tests supported call forms.
src/index.ts Registers the rule.
README.md Documents synchronous execution rules.
eslint.config.cjs Enables the rule as a warning.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (2)

eslint-factory/src/rules/require-execfilesync-try-catch.ts:155

  • The rule drops its advertised suggestion whenever findEnclosingStatement returns null. This happens for both target violations in build_checkout_manifest.cjs: each call is the concise body of an arrow function inside a variable declaration, and the shared helper only recognizes expression/return statements. Those calls are reported with suggest: [], so the main corpus examples cannot receive the promised try/catch rewrite. Add safe handling for concise arrow bodies (converting the body to a block with a protected return), with a regression test for this corpus shape.
          suggest: stmt

eslint-factory/README.md:428

  • Node does not expose a ChildProcessError class for execFileSync; the API throws an Error carrying the synchronous child-process result. Referring to a nonexistent type makes the documentation inaccurate.
Why: `execFileSync` has identical throw-on-failure semantics to `execSync` — it throws a `ChildProcessError` when the child process exits with a non-zero status code or is killed by a signal. An unhandled throw crashes the action without surfacing a useful diagnostic.
  • Files reviewed: 5/5 changed files
  • Comments generated: 3
  • Review effort level: Medium

Comment on lines +129 to +130
{
code: `const { execFileSync: run } = require("child_process"); run("git", ["status"]);`,
docs: {
description:
"Require execFileSync calls in actions/setup/js scripts to be wrapped in try/catch. " +
"execFileSync throws a ChildProcessError when the child process exits with a non-zero status code or is killed by a signal; " +
Comment thread eslint-factory/README.md Outdated

Require `execSync` calls sourced from `child_process` to be wrapped in `try/catch`.

Why: `execSync` throws a `ChildProcessError` when the child process exits with a non-zero status code or is killed by a signal. An unhandled throw crashes the action without surfacing a useful diagnostic.
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #47251 does not have the 'implementation' label and has 0 new lines of code in business logic directories (threshold: 100).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

The rule implementation is functionally correct — binding detection, scope walking, and try-block guard all look solid. Two issues flagged by pre-existing inline comments should be fixed before merge:

  1. ChildProcessError does not exist in Node.js (rule message, docs) — execFileSync throws a plain Error with child-process result fields attached. Using a nonexistent type name misleads users.
  2. Alias test gap — the member-expression alias path (const run = cp.execFileSync; run(...)) is implemented but untested.

Neither prevents the rule from working at runtime, but both should be addressed.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 15.7 AIC · ⌖ 5.57 AIC · ⊞ 5K

@github-actions github-actions Bot mentioned this pull request Jul 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100 — Excellent

Analyzed 5 test(s): 5 design, 0 implementation, 0 violation(s).

📊 Metrics (5 tests)
Metric Value
Analyzed 5 (JS: 5)
✅ Design 5 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 5 (100%)
Duplicate clusters 0
Inflation No (0.81:1 < 2:1)
🚨 Violations 0

Test Coverage Breakdown

Test File Classification Scope
valid: CommonJS destructured require-execfilesync-try-catch.test.ts:20 Design Core behavior with 3 valid patterns
valid: CommonJS namespace require-execfilesync-try-catch.test.ts:31 Design Namespace import variant
valid: ES module require-execfilesync-try-catch.test.ts:38 Design ESM support with 2 patterns
valid: non-child_process ignored require-execfilesync-try-catch.test.ts:45 Design False-positive prevention (critical)
invalid: error detection & suggestions (3x) require-execfilesync-try-catch.test.ts:59–143 Design Error detection + auto-fix validation

Strengths

High behavioral coverage: All 5 tests verify user-visible contracts (error detection, auto-fix suggestions)
Module format diversity: Comprehensive CJS (destructured + namespace) and ESM support
False-positive prevention: Explicitly tests that non-child_process modules are not flagged
Error handling: All error cases include suggestion output validation
No test inflation: 146 test lines vs 180 rule lines (0.81:1 ratio, well under 2:1)
Alias support: Tests aliased function names (execFileSync: run)

Verdict

All quality gates passed. 0% implementation tests (threshold: 30%). No violations.

🧪 Test quality analysis by Test Quality Sentinel · haiku45 12.7 AIC · ⌖ 7.83 AIC · ⊞ 7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /tdd and /codebase-design — requesting changes on correctness, test coverage, and duplication.

📋 Key Themes & Highlights

Blocking Issues

  • Incorrect error type (×2): README and rule message reference ChildProcessError, which does not exist in Node's public API. Node throws an Error with child-process result fields augmented on it. This affects docs and the auto-suggested catch handler in the rule message.
  • Code duplication: The new require-execfilesync-try-catch.ts is a near-verbatim copy of require-execsync-try-catch.ts. The shared binding-resolution helpers should be extracted to try-catch-rule-utils.ts to prevent silent divergence.
  • Missing test: The member-expression alias path (const run = cp.execFileSync; run(...)) is documented but has no invalid test case.
  • Misleading test label: The test "aliased execFileSync" only covers destructuring aliasing, not the distinct member-expression alias form.

Positive Highlights

  • ✅ The rule correctly handles all four documented binding forms (destructured, namespace, aliased, ESM)
  • ✅ Good use of the existing buildTryCatchSuggestion / findEnclosingStatement utilities
  • ✅ Clean integration: wired into index.ts and eslint.config.cjs correctly

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 34.5 AIC · ⌖ 4.83 AIC · ⊞ 6.7K
Comment /matt to run again

@@ -0,0 +1,180 @@
import { AST_NODE_TYPES, ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] This file is nearly identical to require-execsync-try-catch.ts — only the function name (execFileSync vs execSync) differs across ~180 lines. The binding-resolution helpers (isRequireChildProcess, isChildProcessImportBinding, isChildProcessObjectBinding) are copy-pasted verbatim and will silently diverge.

💡 Suggested approach

Extract a generic factory into try-catch-rule-utils.ts:

export function createChildProcessSyncRule(fnName: string, ruleName: string): Rule.RuleModule { ... }

Then both rules become one-liners:

export const requireExecFileSyncTryCatchRule = createChildProcessSyncRule('execFileSync', 'require-execfilesync-try-catch');

This eliminates two maintenance surfaces where fixes/improvements to binding resolution must be applied twice.

@copilot please address this.

output: `import { execFileSync } from "child_process"; try {\n execFileSync("git", ["status"]);\n} catch (err) {\n // TODO: handle execFileSync failure (non-zero exit / signal termination).\n throw new Error(\n "execFileSync failed: " + (err instanceof Error ? err.message : String(err)),\n { cause: err },\n );\n}`,
},
],
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The test title says "aliased execFileSync without try/catch is flagged" but only covers destructuring-alias (const { execFileSync: run } = require(...)). The implementation also handles the member-expression alias path (const run = cp.execFileSync) documented in both the README and source — that path has no invalid test.

💡 Suggested test to add
it('invalid: member-expression alias without try/catch is flagged', () => {
  cjsRuleTester.run('require-execfilesync-try-catch', requireExecFileSyncTryCatchRule, {
    valid: [],
    invalid: [
      {
        code: `const cp = require('child_process'); const run = cp.execFileSync; run('git', ['status']);`,
        errors: [{ messageId: 'requireTryCatch' }],
      },
    ],
  });
});

Without this, a regression in isChildProcessObjectBinding for the alias path would go undetected.

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 100/100. 0% implementation tests (threshold: 30%). All quality gates passed.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request Changes

The rule logic and intent are correct, but three issues need fixing before merge.

Blocking issues (2 medium + pre-existing comments)
  1. Code duplication (new comment, line 10 of rule file): isRequireChildProcess, isChildProcessImportBinding, and isChildProcessObjectBinding are verbatim copies from require-execsync-try-catch.ts. These must be extracted to shared utils — bug fixes to one file will silently not apply to the other.

  2. Missing node:child_process invalid test (new comment, test line 120): Invalid-case tests only cover child_process, not node:child_process. A regression in specifier handling for the node: prefix in the flagging path would go undetected.

  3. ChildProcessError is a non-existent type (existing comments): Both the rule description and the README refer to ChildProcessError, which Node.js does not expose publicly. execFileSync throws a plain Error with augmented fields (.status, .signal, .stderr, .stdout). Fix the wording.

  4. Member-expression alias test is missing (existing comment): const run = cp.execFileSync; run(...) is documented as a supported detection form but has no test coverage.

🔎 Code quality review by PR Code Quality Reviewer · sonnet46 39.1 AIC · ⌖ 4.78 AIC · ⊞ 5.6K
Comment /review to run again


type SourceCodeScope = ReturnType<TSESLint.SourceCode["getScope"]>;

function isRequireChildProcess(node: TSESTree.Node | null | undefined): boolean {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entire rule body duplicated from require-execsync-try-catch.ts — any bug fix to shared helpers will only apply to whichever file is edited, silently leaving the other broken.

💡 Details

isRequireChildProcess, isChildProcessImportBinding, and isChildProcessObjectBinding are verbatim copies of the same helpers in require-execsync-try-catch.ts. Consequences:

  • Adding a new child process specifier, fixing a scope-walk bug, or changing the alias-detection logic must be done twice or one rule silently regresses.
  • These helpers are pure utility functions with no rule-specific behavior — they belong in try-catch-rule-utils.ts (or a dedicated child-process-utils.ts) and should be imported by both rules.

The two rules differ only in the target function name (execSync vs execFileSync) and their diagnostic messages. All detection logic should be shared.

],
},
],
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing invalid-case test for node:child_process namespace form — a regression in the node: prefix path for namespace requires would go undetected.

💡 Details

The valid-case tests include node:child_process (lines 113, 129), but the invalid cases only test child_process. There is no test asserting that:

const cp = require('node:child_process');
cp.execFileSync('git', ['status']); // should be flagged

Add at least one invalid case using the node: specifier with namespace access to ensure CHILD_PROCESS_SPECIFIERS is applied symmetrically for the flagging path, not just the pass-through path.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address the unresolved review feedback below, and rerun checks once the branch is up to date.

Unresolved reviews:

Generated by 👨‍🍳 PR Sous Chef · gpt54 9.56 AIC · ⌖ 8.4 AIC · ⊞ 7K ·
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address the unresolved review feedback below, and rerun checks once the branch is up to date.

Unresolved reviews:...

Addressed the unresolved review feedback in commit 6b65361.

Updates include:

  • extracted duplicated child_process binding helpers into shared utils and reused them in both execSync/execFileSync rules
  • corrected docs/rule wording to refer to Error with child-process result fields (not ChildProcessError)
  • added missing invalid tests for member-expression alias and node:child_process namespace path

I also reran local checks (make agent-report-progress) and reran validation (Code Review + CodeQL), both clean.

CI on this new HEAD is stale from the agent push and needs a maintainer re-trigger.

Copilot AI requested a review from gh-aw-bot July 22, 2026 10:27
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot merge main and recompile

…y-cover-execfilesync

# Conflicts:
#	eslint-factory/eslint.config.cjs
#	eslint-factory/src/index.ts

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@copilot merge main and recompile

Done in dd6b56f: merged origin/main into this branch, resolved conflicts in eslint-factory/src/index.ts and eslint-factory/eslint.config.cjs keeping both main and PR rule entries, then ran make recompile.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address the unresolved review feedback below, and rerun checks once the branch is up to date.

Unresolved reviews:

Generated by 👨‍🍳 PR Sous Chef · gpt54 14.4 AIC · ⌖ 7.63 AIC · ⊞ 7K ·
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address the unresolved review feedback below, and rerun checks once the branch is up to date.

Unresolved reviews:...

Done in 2f0c611: I ran the pr-finisher pass, verified the previously requested review items are already present on this branch (shared child_process helpers, corrected Error wording, member-expression alias + node:child_process invalid tests), and reran checks locally with make agent-report-progress (pass). I also inspected prior CI runs/logs; current CI on this HEAD is stale after agent pushes and needs a maintainer re-trigger.

@pelikhan
pelikhan merged commit db8881e into main Jul 22, 2026
@pelikhan
pelikhan deleted the copilot/eslint-factory-cover-execfilesync branch July 22, 2026 12:29
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.83.0

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[eslint-factory] Cover execFileSync in require-execsync-try-catch (throwing child_process sync sibling)

4 participants