Skip to content

Harden withRetry for transient fetch failures - #61439

Merged
pelikhan merged 5 commits into
mainfrom
copilot/harden-withretry-implementation
Sep 17, 2026
Merged

pelikhan merged 5 commits into
mainfrom
copilot/harden-withretry-implementation

Conversation

Copilot AI commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

withRetry relied on error-message matching and could miss transient HTTP failures or mishandle server-directed delays. This change makes retries predictable across Fetch and Octokit error shapes.

Changes

  • Transient failures

    • Recognize HTTP 408, 425, 429, 500, 502, 503, and 504 by status.
    • Preserve non-retryable handling for other client errors.
  • Server-directed backoff

    • Honor Retry-After for 429, GitHub secondary-rate-limit 403, and 503.
    • Support both Fetch Headers and plain header objects.
    • Keep x-ratelimit-reset specific to rate-limit responses.
  • Retry safety

    • Validate retry counts, delays, multipliers, and predicates before execution.
    • Cap jittered delays at maxDelayMs.
await withRetry(fetchConfig, {
  maxRetries: 3,
  initialDelayMs: 1000,
  maxDelayMs: 10_000,
  backoffMultiplier: 2,
  jitterMs: 100,
});

pr-sous-chef

Run: https://github.com/github/gh-aw/actions/runs/35164374706

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • github.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "github.com"

See Network Configuration for more information.

Generated by 👨‍🍳 PR Sous Chef · pi · gpt54 · 17.8 AIC · ⊞ 9.4K · ◷
Comment /souschef to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI requested a review from pelikhan September 16, 2026 23:31
@pelikhan
pelikhan marked this pull request as ready for review September 16, 2026 23:32
Copilot AI balanced review requested due to automatic review settings September 16, 2026 23:32
@github-actions

github-actions Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Design Decision Gate 🏗️. Review the logs for details.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories.

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

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

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "registry.npmjs.org"

See Network Configuration for more information.

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

@github-actions

github-actions Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

✅ Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch test file diff

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

✅ Ponytail Reviewer completed successfully!

Lean already. Ship.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • ab.chatgpt.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "ab.chatgpt.com"

See Network Configuration for more information.

Generated by Ponytail Reviewer for #61439

@github-actions

github-actions Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ PR Code Quality Reviewer failed during code quality review.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

🔎 Code quality review by PR Code Quality Reviewer

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.

🟡 Changes recommended

Delay validation permits timer overflow, and unrelated action downgrades should be removed or justified.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Hardens JavaScript retry handling for transient HTTP failures and server-directed backoff.

Changes:

  • Adds status-based transient failure detection and broader Retry-After support.
  • Validates retry configuration and caps jittered delays.
  • Updates tests and Docker action pins.
File summaries
File Description
actions/setup/js/error_recovery.cjs Implements retry hardening.
actions/setup/js/error_recovery.test.cjs Tests new retry behavior.
actions/setup/js/create_issue.test.cjs Updates delay-cap expectations.
.github/aw/actions-lock.json Downgrades two Docker action pins.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 3
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.

Comment on lines +338 to +343
for (const key of ["maxRetries", "initialDelayMs", "maxDelayMs", "jitterMs"]) {
const value = config[key];
if (!Number.isSafeInteger(value) || value < 0) {
throw new RangeError(`Retry configuration ${key} must be a non-negative safe integer`);
}
}
Comment thread .github/aw/actions-lock.json Outdated
Comment on lines +121 to +124
"docker/build-push-action@v7.3.0": {
"repo": "docker/build-push-action",
"version": "v7.4.0",
"sha": "c3c9e263c25d99ce0380d002d59b67737d91b0dc"
"version": "v7.3.0",
"sha": "53b7df96c91f9c12dcc8a07bcb9ccacbed38856a"
Comment thread .github/aw/actions-lock.json Outdated
Comment on lines +136 to +139
"docker/setup-buildx-action@v4.3.0": {
"repo": "docker/setup-buildx-action",
"version": "v4.4.0",
"sha": "594f3bf4285d9ea8dc53c9a0c9c4092420091003"
"version": "v4.3.0",
"sha": "37fe631027851001ddb9b187196cc803df7f5f0e"

@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.

Reviewed the withRetry hardening changes for transient fetch failures using the impeccable harden + audit lenses (bug-fix/error-state change).

Logic review (error_recovery.cjs):

  • Status-based transient detection (408/425/429/500/502/503/504), Fetch Headers-aware header reads, Retry-After support extended to secondary-rate-limit 403 and 503, and maxDelayMs capping after jitter all check out against the accompanying unit tests and manual verification.
  • validateRetryConfig correctly rejects malformed retry configs before executing the operation.

One blocking issue found: .github/aw/actions-lock.json unintentionally reverts the docker action pin bump from #61359 (merged just before this branch) — see inline comment. This file is generated by make sync-action-pins, so it's very likely a stale-branch artifact, but as committed it downgrades docker/build-push-action and docker/setup-buildx-action pins.

Warning

Firewall blocked 2 domains

The following domains were blocked by the firewall during workflow execution:

  • codeload.github.com
  • github.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "codeload.github.com"
    - "github.com"

See Network Configuration for more information.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · copilot · sonnet50 · 155.5 AIC · ⌖ 13.7 AIC · ⊞ 8.4K

Comment thread .github/aw/actions-lock.json Outdated
"sha": "22d081ff2d3a40755e97629de92e3bcbfa7cf2ed"
},
"docker/build-push-action@v7.4.0": {
"docker/build-push-action@v7.3.0": {

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.

This PR reverts the docker action pin bump from #61359 (merged just before this branch): docker/build-push-action v7.4.0→v7.3.0 and docker/setup-buildx-action v4.4.0→v4.3.0, including their SHAs. This file is machine-generated (make sync-action-pins), so it's likely just a stale branch/merge artifact rather than an intentional change, but merging as-is will silently downgrade these pinned actions.

Please rebase onto latest main (or regenerate via gh aw compile) so this file reflects the current pins instead of reverting them.

@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.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /codebase-design (classified by pr-triage as bug_fix) — requesting changes on the widened 403 rate-limit heuristic and an unrelated lock-file downgrade.

📋 Key Themes & Highlights

Key Themes

  • Widened 403 rate-limit heuristic in getRetryAfterMs/isRateLimitError: treating "no x-ratelimit-remaining header + retry-after present" as a secondary rate limit is a behavioral change with no authoritative signal behind it and no dedicated regression test for the false-positive case.
  • Duplicated classification logic between getRetryAfterMs and isRateLimitError — same heuristic copy-pasted, risk of drift on future edits.
  • Unrelated lock-file downgrade in .github/aw/actions-lock.json (docker action versions) — looks like a stale-branch artifact unrelated to the withRetry hardening.

Positive Highlights

  • ✅ New TRANSIENT_HTTP_STATUSES set cleanly replaces brittle message-text matching for standard HTTP codes.
  • ✅ getHeader() correctly abstracts Fetch Headers vs. plain objects, with solid test coverage (it("should read case-insensitive headers from a Fetch Headers instance")).
  • ✅ validateRetryConfig and the jitter cap (Math.min(delay + jitter, maxDelayMs)) are well tested with explicit edge cases (Number.NaN, Infinity, non-integer, negative).
  • ✅ Existing create_issue.test.cjs was correctly updated to reflect the new jitter-cap behavior.

@copilot please address the review comments above.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "registry.npmjs.org"

See Network Configuration for more information.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet50 · 168.9 AIC · ⌖ 15.3 AIC · ⊞ 10.4K
Comment /matt to run again

Comment thread actions/setup/js/error_recovery.cjs Outdated
const remainingHeader = getHeader(headers, "x-ratelimit-remaining");
const retryAfter = getHeader(headers, "retry-after");
const remainingExhausted = remainingHeader != null && parseInt(remainingHeader, 10) === 0;
const isRateLimitStatus = status === 429 || (status === 403 && (remainingExhausted || (remainingHeader == null && retryAfter != null)));

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.

[/diagnosing-bugs] Widening the 403 secondary-rate-limit heuristic to also match "no x-ratelimit-remaining header + retry-after present" risks misclassifying ordinary permission/auth 403s (e.g. from a proxy or WAF) that happen to send a retry-after header as GitHub rate-limit errors — triggering the E010 RATE_LIMIT_EXCEEDED code and honoring server-directed delay for a non-rate-limit failure.

💡 Why this matters & suggested fix

The original code only trusted x-ratelimit-remaining: 0 as the secondary-rate-limit signal for 403s, which is authoritative (GitHub always sets it to 0 on secondary rate limits). The new fallback branch (remainingHeader == null && retryAfter != null) has no such guarantee — any 403 with a retry-after header (e.g. from an intermediary) now gets treated as a rate limit.

This logic is duplicated between getRetryAfterMs and isRateLimitError (lines 177 and 223), so the same risk applies to both the delay calculation and the E010 classification.

Consider requiring the error message to also contain a rate-limit indicator (hasRateLimitIndicator) when the x-ratelimit-remaining header is absent, or add a regression test with a 403 + retry-after that is not a rate limit (e.g. an auth failure) to confirm it isn't misclassified.

@copilot please address this.

Comment thread actions/setup/js/error_recovery.cjs Outdated
const remainingHeader = getHeader(headers, "x-ratelimit-remaining");
const retryAfterHeader = getHeader(headers, "retry-after");
const remainingExhausted = remainingHeader != null && parseInt(remainingHeader, 10) === 0;
const hasRateLimitHeaders = status === 403 && (remainingExhausted || (remainingHeader == null && retryAfterHeader != null));

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] The secondary-rate-limit-detection heuristic (remainingExhausted || (remainingHeader == null && retryAfter != null)) is copy-pasted between getRetryAfterMs (line 177) and isRateLimitError (line 223). Extract it into one shared helper (e.g. isSecondaryRateLimit403(status, headers)) so both call sites can't drift out of sync.

💡 Suggested refactor
function isSecondaryRateLimit403(status, headers) {
  if (status !== 403) return false;
  const remainingHeader = getHeader(headers, "x-ratelimit-remaining");
  const retryAfter = getHeader(headers, "retry-after");
  const remainingExhausted = remainingHeader != null && parseInt(remainingHeader, 10) === 0;
  return remainingExhausted || (remainingHeader == null && retryAfter != null);
}

Then both getRetryAfterMs and isRateLimitError call this single source of truth, eliminating the risk of the two implementations diverging on a future edit.

@copilot please address this.

Comment thread .github/aw/actions-lock.json Outdated
"sha": "22d081ff2d3a40755e97629de92e3bcbfa7cf2ed"
},
"docker/build-push-action@v7.4.0": {
"docker/build-push-action@v7.3.0": {

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.

[/diagnosing-bugs] This PR downgrades docker/build-push-action (v7.4.0→v7.3.0) and docker/setup-buildx-action (v4.4.0→v4.3.0), unrelated to the stated withRetry hardening. This looks like a stale branch/merge artifact rather than an intentional change.

💡 Suggested fix

Rebase onto latest main (or re-run the lock-file generator) so this file only reflects intentional changes, or drop this hunk from the PR if it isn't needed.

@copilot please address this.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

pr-sous-chef
@copilot this PR still needs forward progress before a maintainer can investigate quickly. Please address the current blocking review feedback, remove or justify the unrelated .github/aw/actions-lock.json downgrade, fix the failing checks below, refresh the branch if needed, and then run the pr-finisher skill.

Failed checks:

Latest blocking review points:

  • tighten or justify the widened 403 + Retry-After rate-limit heuristic
  • deduplicate the copied classification logic between getRetryAfterMs and isRateLimitError
  • remove the stale lock-file downgrade if it is unrelated to the retry hardening change

Run URL: https://github.com/github/gh-aw/actions/runs/35164374706

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • github.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "github.com"

See Network Configuration for more information.

Generated by 👨‍🍳 PR Sous Chef · pi · gpt54 · 17.8 AIC · ⊞ 9.4K · ◷
Comment /souschef to run again

Copilot AI and others added 3 commits September 17, 2026 00:07
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

pr-sous-chef
@copilot this PR still needs forward progress before a maintainer can investigate quickly. Please address the current blocking review feedback, remove or justify the unrelated `.github/...

Addressed in 62b4dd5: restored the unrelated generated pin/lock changes, limited timer delays to 2,147,483,647 ms, centralized and tightened 403 rate-limit classification, and updated the affected test. Local pre-PR validation passes; the listed CI failures were cache misses, while the shard regression was corrected.

@pelikhan
pelikhan merged commit 290730b into main Sep 17, 2026
11 checks passed
@pelikhan
pelikhan deleted the copilot/harden-withretry-implementation branch September 17, 2026 02:26
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.89.17

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants