Skip to content

fix(actions): retry 504 and time out fetches in dispatch-run - #89

Open
saturninoabril wants to merge 2 commits into
mainfrom
cursor/gh-action-dispatch-run-retry-a8f4
Open

fix(actions): retry 504 and time out fetches in dispatch-run#89
saturninoabril wants to merge 2 commits into
mainfrom
cursor/gh-action-dispatch-run-retry-a8f4

Conversation

@saturninoabril

Copy link
Copy Markdown
Collaborator

Summary

Hardens the reusable test-system-io-dispatch-run action — the orchestrated CI worker — against gateway timeouts, which is where the orchestrated upload path was weakest.

  • The end-of-run shard upload (/reports/register + multipart /reports/upload/...) went through fetchWithAuthRetry, which only retried network-layer errors. An HTTP 504 from the load balancer on a large multipart upload failed the job immediately. This brings the action to parity with test-system-io-report-upload: retry 408/429/502/503/504 with exponential backoff + Retry-After.
  • No fetch had a request timeout, so a hung request waited on the load balancer's idle timeout before surfacing. Every fetch now carries an AbortSignal.timeout (30s for JSON control calls, 120s for multipart uploads); a timeout is classified as a retryable transient failure, so it fails fast and retries instead of hanging.
  • /checkout and /complete (main.ts postJSON) previously re-looped on all 5xx. Since the auth layer now retries the transient gateway statuses itself, the call-site loop skips those (via the new isTransientHTTPStatus) to avoid double-retry amplification, while still covering the remaining 5xx for these idempotent calls.
  • Rebuilt the committed dist/index.js bundle (tsup).

No action.yml input/output changes; behavior change is limited to retry/timeout resilience.

Test Plan

  • tsc -b, oxlint, oxfmt --check all clean; tsup build succeeds and the rebuilt bundle contains the new logic (AbortSignal.timeout, retry-after, transient-status retry).
  • Behavioral verification against a local server (temporary harness, not committed): a 504 → 504 → 200 sequence was retried to a final 200 (3 server hits); isTransientHTTPStatus classifies 504 transient / 500 not; a request to an always-slow endpoint with a 200ms timeout threw TimeoutError after being retried and gave up at ~4.3s (well before the 5s server delay), proving the timeout aborts and retries rather than hanging.

Release Note

The test-system-io-dispatch-run action now retries gateway timeouts (504) on shard uploads and bounds each request with a timeout, reducing spurious CI job failures.
Open in Web Open in Cursor 

The dispatch-run worker's end-of-run shard upload (register + multipart)
only retried network-layer errors, so a 504 from the load balancer on a
large multipart upload failed the job immediately — the weakest link on
the orchestrated path. It also set no request timeout, so a hung request
waited on the LB idle timeout before surfacing.

- auth.ts: retry transient HTTP statuses (408/429/502/503/504) with
  exponential backoff + Retry-After, matching the report-upload action;
  treat request timeouts / aborts as retryable network errors; export
  request-timeout constants, timeoutSignal(), and isTransientHTTPStatus().
- upload.ts / main.ts: attach AbortSignal.timeout to every fetch (30s for
  JSON control calls, 120s for multipart uploads) so a hung request fails
  fast and is retried instead of waiting on the gateway.
- main.ts: /checkout and /complete no longer re-loop on the transient
  statuses the auth layer now handles, avoiding double-retry amplification
  while still covering the other 5xx for these idempotent calls.
- Rebuilt dist/index.js (tsup).

Co-authored-by: saturnino <saturnino@mattermost.com>
@cursor

cursor Bot commented Jul 24, 2026

Copy link
Copy Markdown

@coderabbitai review

@saturninoabril
saturninoabril marked this pull request as ready for review July 24, 2026 09:43
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 51 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c1737249-f1e3-4409-b3cb-3873b05aabc3

📥 Commits

Reviewing files that changed from the base of the PR and between 1afd2a2 and 07700fa.

⛔ Files ignored due to path filters (1)
  • .github/actions/test-system-io-dispatch-run/dist/index.js is excluded by !**/dist/**
📒 Files selected for processing (3)
  • .github/actions/test-system-io-dispatch-run/src/auth.ts
  • .github/actions/test-system-io-dispatch-run/src/main.ts
  • .github/actions/test-system-io-dispatch-run/src/upload.ts
📝 Walkthrough

Walkthrough

Retry handling now supports transient HTTP responses, Retry-After delays, response draining, and expanded network errors. Dispatch and upload requests now use explicit JSON or multipart request timeouts.

Changes

Retry and timeout controls

Layer / File(s) Summary
Transient retry engine
.github/actions/test-system-io-dispatch-run/src/auth.ts
Adds transient status classification and timeout utilities, expands retryable errors, applies exponential backoff with Retry-After, drains response bodies, and returns the final transient response.
Dispatch request integration
.github/actions/test-system-io-dispatch-run/src/main.ts
Adds JSON request timeouts and prevents the outer retry path from duplicating transient statuses handled by the auth layer.
Upload request integration
.github/actions/test-system-io-dispatch-run/src/upload.ts
Adds timeout signals to multipart upload and JSON registration requests.
Estimated code review effort: 3 (Moderate) ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Action
  participant fetchWithRetry
  participant HTTPEndpoint
  participant RetryDelay
  Action->>fetchWithRetry: Send authenticated request
  fetchWithRetry->>HTTPEndpoint: Execute request
  HTTPEndpoint-->>fetchWithRetry: Return transient response or network error
  fetchWithRetry->>RetryDelay: Wait using backoff and Retry-After
  RetryDelay-->>fetchWithRetry: Resume retry
  fetchWithRetry-->>Action: Return response or final error
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: retrying 504s and adding fetch timeouts in the dispatch-run action.
Description check ✅ Passed The description is directly related to the code changes and accurately describes the retry and timeout hardening.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/gh-action-dispatch-run-retry-a8f4

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.

Actionable comments posted: 2

🤖 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 @.github/actions/test-system-io-dispatch-run/src/auth.ts:
- Around line 70-83: Cap the delay returned by parseRetryAfterMs to an
action-appropriate maximum before it is used for sleeping. Apply the same upper
bound to both numeric seconds and parsed HTTP-date values, while preserving the
existing zero handling for missing, invalid, or past Retry-After values.
- Around line 103-113: Update fetchWithAuthRetry in
.github/actions/test-system-io-dispatch-run/src/auth.ts at lines 103-113 and its
callers in .github/actions/test-system-io-dispatch-run/src/main.ts at lines
362-365 and .github/actions/test-system-io-dispatch-run/src/upload.ts at lines
201-204 so response body consumption occurs inside the retried request, or
expose a helper returning parsed JSON. Ensure stalls while reading the body
remain covered by the 30s abort and trigger retries for checkout, complete, and
report registration flows; callers should use the new result without performing
a separate res.text() outside the retry loop.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b645e96f-9963-4ace-9970-d27b66ad4869

📥 Commits

Reviewing files that changed from the base of the PR and between cc3234f and 1afd2a2.

⛔ Files ignored due to path filters (1)
  • .github/actions/test-system-io-dispatch-run/dist/index.js is excluded by !**/dist/**
📒 Files selected for processing (3)
  • .github/actions/test-system-io-dispatch-run/src/auth.ts
  • .github/actions/test-system-io-dispatch-run/src/main.ts
  • .github/actions/test-system-io-dispatch-run/src/upload.ts

Comment thread .github/actions/test-system-io-dispatch-run/src/auth.ts
Comment thread .github/actions/test-system-io-dispatch-run/src/auth.ts
…etry

- parseRetryAfterMs now clamps to MAX_RETRY_AFTER_MS (30s) so an upstream
  429/503 with a huge delay/date can't turn a retry into an unbounded stall.
- Add fetchTextWithAuthRetry, which consumes the response body inside the
  retried scope. Previously callers did res.text() after fetchWithAuthRetry
  returned on headers, so a stall mid-body let the request timeout escape the
  retry loop and hard-fail /checkout, /complete, or report registration on the
  first stalled response. main.ts and upload.ts postJSON now use it.
- Extract isRetryableFetchError (shared by both retry loops); add a timeout to
  the best-effort screenshot upload too. Rebuilt dist/index.js.

Co-authored-by: saturnino <saturnino@mattermost.com>
@saturninoabril
saturninoabril requested review from yasserfaraazkhan and removed request for yasserfaraazkhan July 24, 2026 13:43
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.

2 participants