Skip to content

Require positive timeouts for synchronous child processes - #52978

Merged
pelikhan merged 2 commits into
mainfrom
copilot/eslint-factory-fix-timeout-handling
Aug 15, 2026
Merged

Require positive timeouts for synchronous child processes#52978
pelikhan merged 2 commits into
mainfrom
copilot/eslint-factory-fix-timeout-handling

Conversation

Copilot AI commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

require-sync-exec-timeout previously accepted timeout: 0, even though Node treats it as no timeout. This left synchronous child processes able to block indefinitely while appearing compliant.

  • Rule behavior

    • Reject timeout: 0 and negative numeric literals.
    • Continue allowing positive literals and non-literal values that cannot be statically evaluated.
  • Diagnostic

    • Clarify that timeout: 0 disables the timeout and require a positive millisecond value.
  • Regression coverage

    • Add cases for zero, negative, negative-zero, positive, and dynamic timeout values.
execSync(cmd, { timeout: 0 });    // reported
execSync(cmd, { timeout: 5000 }); // allowed
execSync(cmd, { timeout: config.timeout }); // allowed

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix handling of timeout: 0 in require-sync-exec-timeout rule Require positive timeouts for synchronous child processes Aug 15, 2026
Copilot AI requested a review from pelikhan August 15, 2026 22:24
@pelikhan
pelikhan marked this pull request as ready for review August 15, 2026 22:32
Copilot AI balanced review requested due to automatic review settings August 15, 2026 22:32
@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

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

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

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

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

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Ponytail Reviewer completed successfully!

Lean already. Ship.

Generated by Ponytail Reviewer for #52978

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

reviewed_at: 2026-08-15T00:00:00Z
review_event: REQUEST_CHANGES
top_themes:
  - incomplete static validation for invalid timeout expressions
files_reviewed:
  - eslint-factory/src/rules/require-sync-exec-timeout.ts
  - eslint-factory/src/rules/require-sync-exec-timeout.test.ts
comment_count: 0

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

🔎 Code quality review by PR Code Quality Reviewer · gpt54 · 2.83 AIC · ⌖ 5.32 AIC · ⊞ 6.9K ·
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.

Verdict

REQUEST_CHANGES — this tightens the obvious timeout: 0 hole, but it still leaves statically-invalid timeout forms unflagged, so the rule can be bypassed while appearing compliant.

Blocking themes
  • The new check only rejects numeric literals and unary-negative literals.
  • Constant expressions such as NaN, Infinity, 1n, or parenthesized zero/negative values still pass even though they are not valid positive millisecond timeouts for these APIs.
  • That keeps the rule's safety guarantee weaker than the updated diagnostic claims.

🔎 Code quality review by PR Code Quality Reviewer · gpt54 · 2.83 AIC · ⌖ 5.32 AIC · ⊞ 6.9K
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.

The logic is correct and comprehensive. The isMissingTimeout predicate correctly covers all zero/negative cases: numeric literals <= 0, unary - applied to any numeric literal (including -0), and null/undefined. The updated error message accurately describes the requirement. Tests cover all new cases including the edge case of -0. LGTM.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 14.7 AIC · ⌖ 7.03 AIC · ⊞ 5.6K

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

Updates the ESLint rule to require positive synchronous child-process timeouts.

Changes:

  • Rejects zero and negative literal timeouts.
  • Improves the diagnostic message.
  • Adds regression coverage for literal and dynamic values.
Show a summary per file
File Description
eslint-factory/src/rules/require-sync-exec-timeout.ts Validates positive timeout values and updates diagnostics.
eslint-factory/src/rules/require-sync-exec-timeout.test.ts Adds timeout validation test cases.

Review details

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Suppressed comments (2)

eslint-factory/src/rules/require-sync-exec-timeout.ts:132

  • Unary +0 is also statically zero and disables Node's timeout, but this branch only rejects unary -, so execSync(cmd, { timeout: +0 }) remains unreported despite the new positive-timeout contract. Handle unary plus when its numeric literal is zero and add a regression case.
      (value.type === AST_NODE_TYPES.UnaryExpression && value.operator === "-" && value.argument.type === AST_NODE_TYPES.Literal && typeof value.argument.value === "number") ||

eslint-factory/src/rules/require-sync-exec-timeout.ts:154

  • The suggested object order does not guarantee the advertised protection: a following ...otherOptions can overwrite the positive timeout with 0 or undefined. Put the explicit timeout after the spread so the diagnostic's fix always leaves the effective value positive.
        "{{method}}({{arg}}) has no positive `timeout` option. `timeout: 0` disables the timeout; pass `{ timeout: <positive milliseconds>, ...otherOptions }` so a hung or runaway child process cannot block the job indefinitely.",
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment on lines +130 to +134
const isMissingTimeout =
(value.type === AST_NODE_TYPES.Literal && (value.value == null || (typeof value.value === "number" && value.value <= 0))) ||
(value.type === AST_NODE_TYPES.UnaryExpression && value.operator === "-" && value.argument.type === AST_NODE_TYPES.Literal && typeof value.argument.value === "number") ||
(value.type === AST_NODE_TYPES.Identifier && value.name === "undefined");
if (!isMissingTimeout) return true;
}

/** Returns true when the options-object argument for the call statically carries a non-nullish `timeout` property. */
/** Returns true when the options-object argument for the call statically carries a positive `timeout` property. */

@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 — requesting changes on two focused gaps.

📋 Key Themes & Highlights

Issues Found

  • timeout: +0 not caught — the UnaryExpression branch only guards against unary minus; +0 (unary plus on 0) evaluates to 0 at runtime and bypasses the check. Line 132 of the implementation.
  • Missing coverage for execFileSync/spawnSync — the new zero/negative test cases only exercise execSync; the other two methods share the same logic but have no regression tests for this fix.

Positive Highlights

  • ✅ Correct fix for the core bug: timeout: 0 was silently treated as valid despite Node's documented behaviour.
  • ✅ Good diagnostic message improvement — explicitly calling out that timeout: 0 disables the timeout is actionable.
  • -0 (negative zero literal) correctly handled through the UnaryExpression branch.
  • ✅ Non-literal values (userConfig.timeout) correctly remain allowed.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 26.3 AIC · ⌖ 8.53 AIC · ⊞ 7.7K
Comment /matt to run again

if (!isNullish) return true;
const isMissingTimeout =
(value.type === AST_NODE_TYPES.Literal && (value.value == null || (typeof value.value === "number" && value.value <= 0))) ||
(value.type === AST_NODE_TYPES.UnaryExpression && value.operator === "-" && value.argument.type === AST_NODE_TYPES.Literal && typeof value.argument.value === "number") ||

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 UnaryExpression branch catches timeout: -1 and timeout: -0, but timeout: +0 slips through — unary + applied to 0 evaluates to 0 at runtime, which Node treats as "no timeout".

💡 Suggested fix

Evaluate the unary expression numerically instead of only checking operator === "-":

(value.type === AST_NODE_TYPES.UnaryExpression &&
  (value.operator === "-" || value.operator === "+") &&
  value.argument.type === AST_NODE_TYPES.Literal &&
  typeof value.argument.value === "number" &&
  (value.operator === "-" ? -value.argument.value : +value.argument.value) <= 0)

And add a regression test case:

{ code: `const { execSync } = require("child_process"); execSync("git status", { timeout: +0 });`, errors: [{ messageId: "requireTimeout" }] },

@copilot please address this.

code: `const { execSync } = require("child_process"); execSync("git status", { timeout: -0 });`,
errors: [{ messageId: "requireTimeout" }],
},
],

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 new zero/negative timeout test cases only cover execSync. execFileSync and spawnSync share the same hasTimeoutOption path but have no parallel coverage for timeout: 0 or timeout: -1.

💡 Suggested addition

Add at least one case per method to the existing "invalid: execFileSync and spawnSync without timeout option" describe block:

{ code: `const { execFileSync } = require("child_process"); execFileSync("git", ["status"], { timeout: 0 });`, errors: [{ messageId: "requireTimeout" }] },
{ code: `const { spawnSync } = require("child_process"); spawnSync("git", ["status"], { timeout: 0 });`, errors: [{ messageId: "requireTimeout" }] },

@copilot please address this.

@github-actions github-actions Bot mentioned this pull request Aug 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report — PR #52978

Score: 66/100 ⚠️ Acceptable

Summary

This PR adds 4 new test cases that directly verify the stricter timeout validation logic introduced in the source changes. All new tests target high-value edge cases (timeout: 0, timeout: -1, timeout: -0) that now correctly reject non-positive timeout values.

Test Changes

  • Files Changed: 1 test file
  • New Test Cases: 5 (including 1 new case in an existing test + 4 new error scenarios)
  • Test Framework: vitest + ESLint RuleTester
  • Lines Added: 18 (test) vs. 8 (source) — ratio 2.25:1 (acceptable for edge-case focused changes)

Detailed Test Analysis

New and Modified Tests (5 cases)
Test Classification Behavior Verified Value
"non-literal timeout values" — NEW case behavioral_contract { timeout: userConfig.timeout } is accepted (dynamic values can't be statically verified) high_value
"execSync without timeout" — NEW case 1 behavioral_contract { timeout: 0 } is rejected (zero disables timeout) high_value
"execSync without timeout" — NEW case 2 behavioral_contract { timeout: -1 } is rejected (negative is invalid) high_value
"execSync without timeout" — NEW case 3 behavioral_contract { timeout: -0 } is rejected (JavaScript -0 edge case) high_value
Test name refinement implementation Clarifies that "non-literal values, identifiers, and spreads" are not inspectable high_value

Quality Observations

Strengths:

  • Edge-case coverage: Tests specifically verify the new validation rules for non-positive timeouts.
  • High-value additions: All 4 new error cases directly correspond to source changes.
  • Comprehensive patterns: Tests cover destructured imports, namespace binding, and both CJS/ESM.
  • Standard approach: RuleTester with valid/invalid arrays is the correct pattern for ESLint rule testing.

⚠️ Accepted Concerns:

  • Test inflation ratio: 2.25:1 (18 added lines vs. 8 source lines) is slightly elevated but justified by edge-case focus.

Calculation

Design tests: 6/10 (60%)
Edge-case coverage: 4/10 (40%)
Duplicates: 0
Inflation penalty: 0 (acceptable ratio)

Score = (60% × 40) + (40% × 30) + (20 - 0) + 10 = 66

Verdict

No violations — Implementation tests represent ≤30% of coverage; all tests are high-value and directly verify the design contract (synchronous child processes must have positive timeouts).

🧪 Test quality analysis by Test Quality Sentinel · haiku45 · 22.7 AIC · ⌖ 3.67 AIC · ⊞ 7.9K ·
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.

✅ Test Quality Sentinel: 66/100. All new tests are high-value edge-case coverage directly verifying the stricter timeout validation. No implementation ratio violations (≤30% threshold met).

@pelikhan
pelikhan merged commit 2ad0ffd into main Aug 15, 2026
47 checks passed
@pelikhan
pelikhan deleted the copilot/eslint-factory-fix-timeout-handling branch August 15, 2026 22:51
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Quick triage for maintainer-ready follow-up:

Please refresh the branch if needed, address the remaining maintainer-facing follow-up, and run the pr-finisher skill before handing this PR back to maintainers.

Outstanding review items (newest first):

Failed checks from the compact candidate set:

  • None listed.

Branch update was requested automatically for this run when GitHub allows it.
Run context: https://github.com/github/gh-aw/actions/runs/31912931553

Generated by 👨🍳 PR Sous Chef
Comment /souschef to run again

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 8.98 AIC · ⌖ 7.62 AIC · ⊞ 8.7K ·
Comment /souschef to run again

@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.87.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: require-sync-exec-timeout treats explicit timeout: 0 as satisfying the requirement, but 0 means "no timeout"

4 participants