[eslint-miner] feat(eslint): add require-spawnsync-error-check rule#45246
Conversation
Adds a new ESLint rule that flags spawnSync() calls whose result variable never has its .error property checked. ## Rationale When spawnSync() cannot spawn the child process (ENOENT, ETIMEDOUT, EACCES), the Node.js spec sets result.status = null and result.error to the Error instance. Code that only checks result.status !== 0 will silently pass the guard (null !== 0 is true, so the check fires — but then throws a confusing 'exit null' message) or, in some patterns, completely miss the failure because null coerces in unexpected ways. The well-implemented helpers (git_helpers.cjs, send_otlp_span.cjs) all check result.error first. Five call sites in apply_samples.cjs and artifact_client.cjs skip this check and are flagged by the new rule. ## Evidence Violations found (warnings): - actions/setup/js/apply_samples.cjs:97 (runGit spawnSync) - actions/setup/js/artifact_client.cjs:163 (ensureZipAvailable) - actions/setup/js/artifact_client.cjs:170 (ensureUnzipAvailable) - actions/setup/js/artifact_client.cjs:183 (createZipFromFiles) - actions/setup/js/artifact_client.cjs:316 (unzipResult) Good patterns that pass (no false positives): - actions/setup/js/git_helpers.cjs (checks result.error) - actions/setup/js/send_otlp_span.cjs (checks result.error) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
|
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. No ADR enforcement needed: PR #45246 does not have the 'implementation' label and has 0 new lines of code in business logic directories (threshold: 100). |
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
|
✅ Test Quality Sentinel completed test quality analysis. |
There was a problem hiding this comment.
Pull request overview
Adds an ESLint rule detecting unchecked spawnSync() errors in setup scripts.
Changes:
- Implements and tests the new rule.
- Registers and enables it as a warning.
Show a summary per file
| File | Description |
|---|---|
eslint-factory/src/rules/require-spawnsync-error-check.ts |
Implements detection logic. |
eslint-factory/src/rules/require-spawnsync-error-check.test.ts |
Adds rule tests. |
eslint-factory/src/index.ts |
Registers the rule. |
eslint-factory/eslint.config.cjs |
Enables the rule. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 4/4 changed files
- Comments generated: 2
- Review effort level: Medium
| if (callee.type === AST_NODE_TYPES.Identifier && callee.name === SPAWNSYNC_NAME) { | ||
| return true; |
| if (!variable) return; | ||
|
|
||
| // Check whether any reference to this variable reads the .error property. | ||
| const hasErrorCheck = variable.references.some(ref => { |
🧪 Test Quality Sentinel Report✅ Test Quality Score: 82/100 — Excellent
📊 Metrics (4 tests)
Verdict
Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
There was a problem hiding this comment.
Review: require-spawnsync-error-check ESLint rule
The rule is well-motivated, correctly scoped, and the implementation is clean. Tests cover the main valid/invalid patterns and I confirmed zero false positives on existing compliant files.
One non-blocking gap flagged inline: the .error detection only covers member-expression access (result.error) and misses the destructuring pattern (const { error } = result), which would produce a false positive. See inline comment for the fix approach.
Everything else (scope walking, namespace matching, message clarity, test coverage) looks good.
🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 19.8 AIC · ⌖ 5.03 AIC · ⊞ 4.8K
| const id = ref.identifier; | ||
| const parent = id.parent; | ||
| return ( | ||
| parent !== undefined && |
There was a problem hiding this comment.
The .error check only detects result.error member-expression accesses, but misses the destructuring pattern:
const result = spawnSync("git", ["status"]);
const { error } = result; // result.error IS read — but the rule flags this as a violation
if (error) throw error;This produces a false positive. Consider also detecting VariableDeclarator nodes whose id is an ObjectPattern that destructures error from the spawnSync result variable, and marking those as satisfying the check.
@copilot please address this.
There was a problem hiding this comment.
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 34.8 AIC · ⌖ 4.6 AIC · ⊞ 6.6K
Comment /matt to run again
| return ( | ||
| parent !== undefined && | ||
| parent.type === AST_NODE_TYPES.MemberExpression && | ||
| !parent.computed && |
There was a problem hiding this comment.
[/tdd] False positive risk: destructuring const { error } = result is a valid error-check pattern but won't be detected here, causing a spurious warning on correct code.
💡 Suggested fix
The current check only recognises result.error MemberExpression access. Add a second branch that detects references where the parent is an ObjectPattern destructuring the result variable:
// also match: const { error } = result
const hasDestructureErrorCheck = variable.references.some(ref => {
const id = ref.identifier;
const parent = id.parent;
return (
parent !== undefined &&
parent.type === AST_NODE_TYPES.VariableDeclarator &&
parent.init === id &&
parent.id.type === AST_NODE_TYPES.ObjectPattern &&
parent.id.properties.some(
p =>
p.type === AST_NODE_TYPES.Property &&
p.key.type === AST_NODE_TYPES.Identifier &&
p.key.name === "error",
)
);
});Combine with hasErrorCheck using ||.
Add a corresponding test case:
`const result = spawnSync("git", ["status"]); const { error } = result; if (error) throw error;`@copilot please address this.
| if (!isSpawnSyncCall(node.init)) return; | ||
|
|
||
| // Only handle simple identifier bindings: const result = spawnSync(...) | ||
| if (node.id.type !== AST_NODE_TYPES.Identifier) return; |
There was a problem hiding this comment.
[/tdd] The rule only tracks simple const result = spawnSync(...) bindings. Re-assignment (result = spawnSync(...)) or let result; result = spawnSync(...) patterns are silently ignored, missing real violations.
💡 Suggested fix or test
Either document the limitation in the rule description/schema, or add an AssignmentExpression visitor. At minimum, add a test asserting the known gap so future contributors are aware:
// Known gap – no error reported for assignment (non-declarator) pattern
`let result; result = spawnSync("git", ["status"]); if (result.status !== 0) throw new Error("failed");`@copilot please address this.
| // Unqualified function name used when spawnSync is destructured from child_process. | ||
| const SPAWNSYNC_NAME = "spawnSync"; | ||
|
|
||
| // Known namespace aliases for the child_process module. |
There was a problem hiding this comment.
[/grill-with-docs] CHILD_PROCESS_OBJECTS is a hard-coded set of aliases. Dynamic imports (const cp = require('child_process') with any alias) are not detected, which is a gap worth documenting.
💡 Suggestion
Add a comment and/or a doc note in the rule's description clarifying that the rule only covers the two well-known aliases (childProcess, child_process) and bare spawnSync calls — not arbitrary require-assigned aliases. This sets correct expectations and prevents confusion when new aliases appear.
@copilot please address this.
|
🎉 This pull request is included in a new release. Release: |
Summary
Adds a new ESLint rule
require-spawnsync-error-checkto thegh-aw-customplugin. The rule enforces that callers ofspawnSync(bare or namespaced viachildProcess/child_process) check the.errorproperty on the result, not only.status.Motivation: When
spawnSynccannot spawn the child process (e.g.ENOENT,ETIMEDOUT),result.statusisnullandresult.errorholds the actualError. Checking onlyresult.statussilently swallows spawn-level failures or emits a misleading "exit null" diagnostic.Changes
eslint-factory/src/rules/require-spawnsync-error-check.tseslint-factory/src/rules/require-spawnsync-error-check.test.tseslint-factory/src/index.tseslint-factory/eslint.config.cjswarnseverityRule behaviour
spawnSync(...),childProcess.spawnSync(...),child_process.spawnSync(...).errorread anywhere in scopemissingErrorCheckwarnValid (no warning):
Invalid (warning emitted):
Test coverage
spawnSync, namespaced variants, indirect property read via intermediate variablespawnSynccalls (execSync, bare identifier reference) are not flaggedImpact
Purely additive. No existing rules modified. No breaking changes.