Skip to content

fix(batch): classify RequestTimeoutError as timeout in --all --wait fan-out#154

Open
Awad-de wants to merge 3 commits into
TestSprite:mainfrom
Awad-de:fix/pr135-clean
Open

fix(batch): classify RequestTimeoutError as timeout in --all --wait fan-out#154
Awad-de wants to merge 3 commits into
TestSprite:mainfrom
Awad-de:fix/pr135-clean

Conversation

@Awad-de

@Awad-de Awad-de commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes empty stdout in --output json mode when test run --all --wait or
test rerun --all --wait hit a per-request timeout during batch fan-out polling.

Before: RequestTimeoutError from any member poll rejected the fan-out
promise before out.print() — stdout empty, agents could not recover runIds.

After: pollFreshAccepted / pollAccepted classify RequestTimeoutError
as status: 'timeout' (mirrors create-batch --run and existing TimeoutError
handling). Fan-out completes, stdout carries every dispatched runId, exit 7
fires with testsprite test wait <runId> hints in stderr.

Complements #153 (single-run TimeoutError) and #155 (single rerun TimeoutError).

Related issue

Fixes #158

Type of change

  • Bug fix (non-breaking change that fixes an issue)

Checklist

  • PR targets the main branch.
  • Commits follow Conventional Commits.
  • npm run lint and npm run typecheck pass.
  • npm test passes (1570 tests).
  • New behavior covered by unit tests in test.run.spec.ts and test.rerun.spec.ts.

Notes for reviewers

  • ~30 lines across two catch blocks in pollFreshAccepted and pollAccepted.
  • Same pattern already used in runBatchRun (create-batch --run) at ~line 2806.
  • BE closure fan-out already had D4 fix for RequestTimeoutError; this closes the
    gap on the --all --wait fresh-run and batch-rerun paths.

Summary by CodeRabbit

  • Bug Fixes
    • Hardened batch test run --wait and test rerun --wait fan-out so RequestTimeoutError is handled per run instead of failing the whole operation.
    • Timed-out runs are now included in the final JSON output with status: "timeout", while retaining the expected exit code and non-empty stdout.
  • Tests
    • Added regression coverage for --wait fan-out behavior when polling times out, including validation of accepted runIds and per-entry timeout status.

…an-out

When test run --all --wait or test rerun --all --wait hit a per-request timeout
during batch fan-out polling, RequestTimeoutError rejected the fan-out before
out.print(). Classify it as status:'timeout' in pollFreshAccepted and
pollAccepted so stdout always lists every dispatched runId.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0fc1fe91-8b07-4f1c-adf9-ddb9b5f32a83

📥 Commits

Reviewing files that changed from the base of the PR and between e108484 and ad85bee.

📒 Files selected for processing (1)
  • src/commands/test.run.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/commands/test.run.spec.ts

Walkthrough

test run --all --wait and test rerun --all --wait now treat polling RequestTimeoutError as per-run timeout results. Both commands still emit JSON output, and the regression tests assert exit code 7 plus timeout-marked accepted runs.

Changes

Per-run timeout handling in batch fan-out

Layer / File(s) Summary
test run --all --wait timeout handling
src/commands/test.ts, src/commands/test.run.spec.ts
pollFreshAccepted now maps RequestTimeoutError to a per-run timeout result with UNSUPPORTED error fields, and the regression test checks exit code 7 plus JSON stdout with both accepted run IDs marked timeout.
test rerun --all --wait timeout handling
src/commands/test.ts, src/commands/test.rerun.spec.ts
pollAccepted now maps RequestTimeoutError to a per-run timeout result with UNSUPPORTED error fields, and the regression test checks exit code 7 plus JSON stdout with both accepted run IDs marked timeout.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: ruili-testsprite, zeshi-du

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main fix: classifying RequestTimeoutError as timeout during batch --all --wait fan-out.
Linked Issues check ✅ Passed The changes match issue #158 by handling RequestTimeoutError in pollFreshAccepted and pollAccepted and adding regression tests.
Out of Scope Changes check ✅ Passed The touched code and tests stay within the batch fan-out timeout fix; no unrelated feature work is evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/commands/test.ts (1)

5459-5474: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Near-duplicate error-classification logic between pollFreshAccepted and pollAccepted.

The RequestTimeoutError branch (and the surrounding TimeoutError/ApiError branches) in pollFreshAccepted (Lines 5459-5474) is essentially identical to the one in pollAccepted (Lines 6627-6642), differing only in runId vs entry.runId. Consider extracting a shared classifyFanOutPollError(err, { testId, runId, timeoutSeconds }) helper to avoid drift between the two copies (e.g., the existing ApiError branches already diverge in exitCode handling between the two functions).

♻️ Suggested shared helper (illustrative)
function classifyFanOutPollError(
  err: unknown,
  entry: { testId: string; runId: string },
  timeoutSeconds: number,
): { testId: string; runId: string; status: string; error: { code: string; message: string; exitCode: number } } {
  if (err instanceof TimeoutError) {
    return {
      testId: entry.testId,
      runId: entry.runId,
      status: 'timeout',
      error: { code: 'UNSUPPORTED', message: `Timed out after ${timeoutSeconds}s`, exitCode: 7 },
    };
  }
  if (err instanceof RequestTimeoutError) {
    return {
      testId: entry.testId,
      runId: entry.runId,
      status: 'timeout',
      error: { code: 'UNSUPPORTED', message: err.message, exitCode: err.exitCode },
    };
  }
  throw err;
}

Also applies to: 6627-6642

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/test.ts` around lines 5459 - 5474, The timeout/error
classification logic in pollFreshAccepted and pollAccepted is duplicated and
already drifting, so extract a shared helper such as classifyFanOutPollError
that handles TimeoutError, RequestTimeoutError, and ApiError consistently.
Update both pollFreshAccepted and pollAccepted to call the helper with their
entry/testId/runId context so the runId difference is localized and future
exitCode or message changes stay in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/commands/test.ts`:
- Around line 5459-5474: The timeout/error classification logic in
pollFreshAccepted and pollAccepted is duplicated and already drifting, so
extract a shared helper such as classifyFanOutPollError that handles
TimeoutError, RequestTimeoutError, and ApiError consistently. Update both
pollFreshAccepted and pollAccepted to call the helper with their
entry/testId/runId context so the runId difference is localized and future
exitCode or message changes stay in one place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e7d396e0-eb26-46a3-8e43-59fff4ffbc20

📥 Commits

Reviewing files that changed from the base of the PR and between e53257d and 94a1d42.

📒 Files selected for processing (3)
  • src/commands/test.rerun.spec.ts
  • src/commands/test.run.spec.ts
  • src/commands/test.ts

@Awad-de

Awad-de commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Discord:
Username = muathawad_37013
ID = Muath Awad

@zeshi-du

zeshi-du commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Approved — this redo is exactly what we asked for: only the claimed fix, mirroring the existing RequestTimeoutError partial-stdout contract. Its sibling (#153) merged today, which is also what made this one go CONFLICTING (same regions of test.ts). One more rebase onto current main and it merges — thanks for sticking with the redo process.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/commands/test.run.spec.ts (1)

3696-3748: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Confirm the new regression test also verifies the documented nextAction hint.

Per the change summary, this block asserts exitCode: 7 and accepted[].status === 'timeout' for the two RequestTimeoutError runs, but doesn't appear to assert the nextAction resume hint (testsprite test wait <runId>) that the batch timeout contract emits (per ApiError.fromEnvelope in runTestRunAll). Since DOCUMENTATION.md specifies this hint as part of the timeout contract, consider asserting error.nextAction (or the equivalent thrown error field) contains both runIds so the contract is fully pinned down by this regression test, not just the exit code and stdout accepted-status shape.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/test.run.spec.ts` around lines 3696 - 3748, The new regression
test for runTestRunAll should also pin the timeout contract’s nextAction resume
hint, not just exitCode: 7 and the accepted[] timeout statuses. Update the test
case in test.run.spec.ts to assert the thrown error from runTestRunAll (or the
ApiError-like object it returns) includes nextAction with the documented
“testsprite test wait <runId>” guidance for both accepted runIds. Use the
existing runTestRunAll and RequestTimeoutError setup so the test fully verifies
the batch timeout behavior end-to-end.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/test.run.spec.ts`:
- Line 8: The test file imports readFileSync from node:fs but never uses it,
triggering ESLint/CI. Remove the unused readFileSync import from the top-level
import statement in test.run.spec.ts, keeping the remaining fs imports used by
the spec.

---

Nitpick comments:
In `@src/commands/test.run.spec.ts`:
- Around line 3696-3748: The new regression test for runTestRunAll should also
pin the timeout contract’s nextAction resume hint, not just exitCode: 7 and the
accepted[] timeout statuses. Update the test case in test.run.spec.ts to assert
the thrown error from runTestRunAll (or the ApiError-like object it returns)
includes nextAction with the documented “testsprite test wait <runId>” guidance
for both accepted runIds. Use the existing runTestRunAll and RequestTimeoutError
setup so the test fully verifies the batch timeout behavior end-to-end.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4e6b3ac4-ca58-4bf7-8b2a-4291fa0fd59e

📥 Commits

Reviewing files that changed from the base of the PR and between 94a1d42 and e108484.

📒 Files selected for processing (3)
  • src/commands/test.rerun.spec.ts
  • src/commands/test.run.spec.ts
  • src/commands/test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/commands/test.rerun.spec.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/commands/test.run.spec.ts (1)

3696-3748: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Confirm the new regression test also verifies the documented nextAction hint.

Per the change summary, this block asserts exitCode: 7 and accepted[].status === 'timeout' for the two RequestTimeoutError runs, but doesn't appear to assert the nextAction resume hint (testsprite test wait <runId>) that the batch timeout contract emits (per ApiError.fromEnvelope in runTestRunAll). Since DOCUMENTATION.md specifies this hint as part of the timeout contract, consider asserting error.nextAction (or the equivalent thrown error field) contains both runIds so the contract is fully pinned down by this regression test, not just the exit code and stdout accepted-status shape.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/test.run.spec.ts` around lines 3696 - 3748, The new regression
test for runTestRunAll should also pin the timeout contract’s nextAction resume
hint, not just exitCode: 7 and the accepted[] timeout statuses. Update the test
case in test.run.spec.ts to assert the thrown error from runTestRunAll (or the
ApiError-like object it returns) includes nextAction with the documented
“testsprite test wait <runId>” guidance for both accepted runIds. Use the
existing runTestRunAll and RequestTimeoutError setup so the test fully verifies
the batch timeout behavior end-to-end.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/test.run.spec.ts`:
- Line 8: The test file imports readFileSync from node:fs but never uses it,
triggering ESLint/CI. Remove the unused readFileSync import from the top-level
import statement in test.run.spec.ts, keeping the remaining fs imports used by
the spec.

---

Nitpick comments:
In `@src/commands/test.run.spec.ts`:
- Around line 3696-3748: The new regression test for runTestRunAll should also
pin the timeout contract’s nextAction resume hint, not just exitCode: 7 and the
accepted[] timeout statuses. Update the test case in test.run.spec.ts to assert
the thrown error from runTestRunAll (or the ApiError-like object it returns)
includes nextAction with the documented “testsprite test wait <runId>” guidance
for both accepted runIds. Use the existing runTestRunAll and RequestTimeoutError
setup so the test fully verifies the batch timeout behavior end-to-end.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4e6b3ac4-ca58-4bf7-8b2a-4291fa0fd59e

📥 Commits

Reviewing files that changed from the base of the PR and between 94a1d42 and e108484.

📒 Files selected for processing (3)
  • src/commands/test.rerun.spec.ts
  • src/commands/test.run.spec.ts
  • src/commands/test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/commands/test.rerun.spec.ts
🛑 Comments failed to post (1)
src/commands/test.run.spec.ts (1)

8-8: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove unused readFileSync import.

Flagged by ESLint/CI as unused.

🧹 Proposed fix
-import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
+import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
🧰 Tools
🪛 ESLint

[error] 8-8: 'readFileSync' is defined but never used.

(@typescript-eslint/no-unused-vars)

🪛 GitHub Actions: CI / 0_Lint & Format.txt

[error] 8-8: ESLint (@typescript-eslint/no-unused-vars): 'readFileSync' is defined but never used.

🪛 GitHub Actions: CI / Lint & Format

[error] 8-8: ESLint (@typescript-eslint/no-unused-vars): 'readFileSync' is defined but never used.

🪛 GitHub Check: Lint & Format

[failure] 8-8:
'readFileSync' is defined but never used

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/test.run.spec.ts` at line 8, The test file imports readFileSync
from node:fs but never uses it, triggering ESLint/CI. Remove the unused
readFileSync import from the top-level import statement in test.run.spec.ts,
keeping the remaining fs imports used by the spec.

Source: Linters/SAST tools

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.

[Hackathon] fix(batch): RequestTimeoutError timeout in --all --wait fan-out (#154)

2 participants