feat: add retry logic with exponential backoff for async operations#82
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a reusable retry helper with exponential backoff to make GitHub API ref updates more resilient to transient failures, and expands the Jest suite to validate the new behavior. This fits into the action’s release/publish flow by reducing flakiness when updating refs via Octokit.
Changes:
- Introduced
retryWithBackoffhelper and wrappedoctokit.rest.git.updateRefwith retries. - Added unit tests for the retry helper and integration-style tests covering
updateRefretry behavior. - Bumped package version to
3.0.1and updated the coverage badge.
Reviewed changes
Copilot reviewed 3 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/index.js |
Adds exported retry helper and uses it when updating branch refs via GitHub API. |
__tests__/index.test.js |
Adds focused tests for retry timing/behavior and validates updateRef retry integration. |
package.json |
Version bump reflecting behavior change. |
package-lock.json |
Lockfile version sync with package bump. |
badges/coverage.svg |
Updates coverage badge output after new tests. |
Comments suppressed due to low confidence (2)
tests/index.test.js:1283
- This test switches to real timers, which will force an actual ~1s sleep due to the default backoff delay and can slow down CI/flakiness. Prefer using fake timers here as well and advancing time (as in the
retryWithBackoffunit tests) so the retry behavior is deterministic and fast.
describe('updateRef retry behavior', () => {
test('should retry updateRef on transient failure', async () => {
jest.useRealTimers();
mockCore.getInput.mockImplementation(name => {
tests/index.test.js:1324
- Using an async function as a
new Promiseexecutor (and suppressing the lint rule) is error-prone and harder to read. You can usually rewrite this asconst runPromise = run();then advance timers andawait runPromise(orawait expect(runPromise).resolves...depending on behavior), avoiding the custom Promise wrapper entirely.
// eslint-disable-next-line no-async-promise-executor
const promise = new Promise(async resolve => {
await run();
resolve();
});
Add isTransientError() to classify retryable errors (429, 5xx, network) vs non-retryable (400, 401, 403, 404, 422). retryWithBackoff() now supports a custom shouldRetry predicate and logs status/code in warnings. The updateRef call uses a custom predicate to also retry 422 'Object does not exist' errors caused by eventual consistency after commit creation.
819d526 to
30ea5a9
Compare
- Guard error.message with optional chaining to handle non-Error throws - Add explicit 'retries exhausted' warning when all attempts fail - Switch updateRef transient test to fake timers (0.44s vs 1.3s)
- Clamp retries to >= 0 so negative values execute fn once instead of silently returning undefined - Switch exhausted-retries test to fake timers with Promise.all pattern to avoid real-time sleeps and unhandled rejection issues
Prevents fake timer leaks if an assertion throws before manual jest.useRealTimers() cleanup.
Coerce retries/baseDelay to finite numbers, falling back to defaults (3 retries, 1000ms delay) for NaN/non-numeric values.
Guards against non-Error throws (null/undefined) in the custom shouldRetry predicate for updateRef.
- Move transientNetworkCodes to a module-level Set constant to avoid per-call allocation - Mark retryWithBackoff options as optional in JSDoc with defaults
📦 Draft Release CreatedA draft release v3.0.1 has been created for this PR. Next Steps
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request introduces a robust retry mechanism with exponential backoff to handle transient failures when updating Git references via the GitHub API. It also adds comprehensive tests for this new functionality and updates the package version. The most important changes are summarized below: