Compare commits

...

3 Commits

Author SHA1 Message Date
Bob Vincent 034e97e9fe
Merge fcdb15c322 into f548e57e54 2026-07-20 23:55:57 +02:00
Aiqiao Yan f548e57e54
Consolidate dependency updates in CHANGELOG (#2537)
* Consolidate dependency updates in CHANGELOG

Removed specific dependency updates and consolidated them into a single entry.

* Fix capitalization in v7.0.1 changelog entries

Updated changelog entries for v7.0.1 to use consistent capitalization.
2026-07-20 12:20:47 -04:00
Vincent, Robert fcdb15c322 Handle checkout 500 retries with backoff
Switch the shared retry helper from randomized delays to exponential backoff so transient GitHub 500 errors are retried predictably. Add coverage for the backoff sequence and regenerate the bundled dist output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-26 16:40:09 -04:00
4 changed files with 77 additions and 35 deletions

View File

@ -1,26 +1,14 @@
# Changelog
## v7.0.1
* Bump github/codeql-action from 3 to 4 by @dependabot[bot] in https://github.com/actions/checkout/pull/2475
* Bump actions/setup-node from 4 to 6 by @dependabot[bot] in https://github.com/actions/checkout/pull/2477
* Bump docker/build-push-action from 6.5.0 to 7.2.0 by @dependabot[bot] in https://github.com/actions/checkout/pull/2478
* Bump docker/login-action from 3.3.0 to 4.2.0 by @dependabot[bot] in https://github.com/actions/checkout/pull/2479
* Bump actions/checkout from 6 to 7 by @dependabot[bot] in https://github.com/actions/checkout/pull/2488
* Bump actions/upload-artifact from 4 to 7 by @dependabot[bot] in https://github.com/actions/checkout/pull/2476
* eslint 9 by @dependabot[bot] in https://github.com/actions/checkout/pull/2474
* Bump the minor-actions-dependencies group with 2 updates by @dependabot[bot] in https://github.com/actions/checkout/pull/2499
* skip running unsafe pr check if input is default by @aiqiaoy in https://github.com/actions/checkout/pull/2518
* trim only ascii whitespace for branch by @aiqiaoy in https://github.com/actions/checkout/pull/2521
* escape values passed to --unset by @aiqiaoy in https://github.com/actions/checkout/pull/2530
* Skip running unsafe pr check if input is default by @aiqiaoy in https://github.com/actions/checkout/pull/2518
* Trim only ascii whitespace for branch by @aiqiaoy in https://github.com/actions/checkout/pull/2521
* Escape values passed to --unset by @aiqiaoy in https://github.com/actions/checkout/pull/2530
* Various dependency updates
## v7.0.0
* Block checking out fork PR for pull_request_target and workflow_run by @aiqiaoy in https://github.com/actions/checkout/pull/2454
* Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the minor-actions-dependencies group across 1 directory by @dependabot[bot] in https://github.com/actions/checkout/pull/2458
* Bump flatted from 3.3.1 to 3.4.2 by @dependabot[bot] in https://github.com/actions/checkout/pull/2460
* Bump js-yaml from 4.1.0 to 4.2.0 by @dependabot[bot] in https://github.com/actions/checkout/pull/2461
* Bump @actions/core and @actions/tool-cache and Remove uuid by @dependabot[bot] in https://github.com/actions/checkout/pull/2459
* upgrade module to esm and update dependencies by @aiqiaoy in https://github.com/actions/checkout/pull/2463
* Bump the minor-npm-dependencies group across 1 directory with 3 updates by @dependabot[bot] in https://github.com/actions/checkout/pull/2462
* Various dependency updates
## v6.0.3
* Fix checkout init for SHA-256 repositories by @yaananth in https://github.com/actions/checkout/pull/2439

View File

@ -23,11 +23,9 @@ jest.unstable_mockModule('@actions/core', () => ({
// Dynamic imports after mocking
const {RetryHelper} = await import('../src/retry-helper.js')
let retryHelper: any
describe('retry-helper tests', () => {
beforeAll(() => {
retryHelper = new RetryHelper(3, 0, 0)
// @actions/core is mocked at module load above; nothing to set up here.
})
beforeEach(() => {
@ -40,14 +38,22 @@ describe('retry-helper tests', () => {
})
it('first attempt succeeds', async () => {
const retryHelper: any = new RetryHelper(3, 1, 10)
const sleep = jest.fn().mockResolvedValue(undefined)
retryHelper.sleep = sleep
const actual = await retryHelper.execute(async () => {
return 'some result'
})
expect(actual).toBe('some result')
expect(info).toHaveLength(0)
expect(sleep).not.toHaveBeenCalled()
})
it('second attempt succeeds', async () => {
const retryHelper: any = new RetryHelper(3, 1, 10)
const sleep = jest.fn().mockResolvedValue(undefined)
retryHelper.sleep = sleep
let attempts = 0
const actual = await retryHelper.execute(() => {
if (++attempts == 1) {
@ -60,10 +66,15 @@ describe('retry-helper tests', () => {
expect(actual).toBe('some result')
expect(info).toHaveLength(2)
expect(info[0]).toBe('some error')
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
expect(info[1]).toBe('Waiting 1 seconds before trying again')
expect(sleep).toHaveBeenCalledTimes(1)
expect(sleep).toHaveBeenCalledWith(1)
})
it('third attempt succeeds', async () => {
const retryHelper: any = new RetryHelper(3, 1, 10)
const sleep = jest.fn().mockResolvedValue(undefined)
retryHelper.sleep = sleep
let attempts = 0
const actual = await retryHelper.execute(() => {
if (++attempts < 3) {
@ -76,12 +87,18 @@ describe('retry-helper tests', () => {
expect(actual).toBe('some result')
expect(info).toHaveLength(4)
expect(info[0]).toBe('some error 1')
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
expect(info[1]).toBe('Waiting 1 seconds before trying again')
expect(info[2]).toBe('some error 2')
expect(info[3]).toMatch(/Waiting .+ seconds before trying again/)
expect(info[3]).toBe('Waiting 2 seconds before trying again')
expect(sleep).toHaveBeenCalledTimes(2)
expect(sleep).toHaveBeenNthCalledWith(1, 1)
expect(sleep).toHaveBeenNthCalledWith(2, 2)
})
it('all attempts fail succeeds', async () => {
const retryHelper: any = new RetryHelper(3, 1, 10)
const sleep = jest.fn().mockResolvedValue(undefined)
retryHelper.sleep = sleep
let attempts = 0
let error: Error = null as unknown as Error
try {
@ -95,8 +112,42 @@ describe('retry-helper tests', () => {
expect(attempts).toBe(3)
expect(info).toHaveLength(4)
expect(info[0]).toBe('some error 1')
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/)
expect(info[1]).toBe('Waiting 1 seconds before trying again')
expect(info[2]).toBe('some error 2')
expect(info[3]).toMatch(/Waiting .+ seconds before trying again/)
expect(info[3]).toBe('Waiting 2 seconds before trying again')
expect(sleep).toHaveBeenCalledTimes(2)
expect(sleep).toHaveBeenNthCalledWith(1, 1)
expect(sleep).toHaveBeenNthCalledWith(2, 2)
})
it('server-side 500 errors are retried with exponential backoff', async () => {
const retryHelper: any = new RetryHelper(4, 2, 10)
const sleep = jest.fn().mockResolvedValue(undefined)
retryHelper.sleep = sleep
let attempts = 0
const actual = await retryHelper.execute(() => {
if (++attempts < 3) {
const error: Error & {status?: number} = new Error(
`server error ${attempts}`
)
error.status = 500
throw error
}
return Promise.resolve('some result')
})
expect(actual).toBe('some result')
expect(attempts).toBe(3)
expect(info).toEqual([
'server error 1',
'Waiting 2 seconds before trying again',
'server error 2',
'Waiting 4 seconds before trying again'
])
expect(sleep).toHaveBeenCalledTimes(2)
expect(sleep).toHaveBeenNthCalledWith(1, 2)
expect(sleep).toHaveBeenNthCalledWith(2, 4)
})
})

10
dist/index.js vendored
View File

@ -35491,7 +35491,7 @@ class retry_helper_RetryHelper {
info(err?.message);
}
// Sleep
const seconds = this.getSleepAmount();
const seconds = this.getSleepAmount(attempt);
info(`Waiting ${seconds} seconds before trying again`);
await this.sleep(seconds);
attempt++;
@ -35499,9 +35499,11 @@ class retry_helper_RetryHelper {
// Last attempt
return await action();
}
getSleepAmount() {
return (Math.floor(Math.random() * (this.maxSeconds - this.minSeconds + 1)) +
this.minSeconds);
getSleepAmount(attempt) {
if (this.minSeconds === 0) {
return 0;
}
return Math.min(this.minSeconds * Math.pow(2, attempt - 1), this.maxSeconds);
}
async sleep(seconds) {
return new Promise(resolve => setTimeout(resolve, seconds * 1000));

View File

@ -33,7 +33,7 @@ export class RetryHelper {
}
// Sleep
const seconds = this.getSleepAmount()
const seconds = this.getSleepAmount(attempt)
core.info(`Waiting ${seconds} seconds before trying again`)
await this.sleep(seconds)
attempt++
@ -43,11 +43,12 @@ export class RetryHelper {
return await action()
}
private getSleepAmount(): number {
return (
Math.floor(Math.random() * (this.maxSeconds - this.minSeconds + 1)) +
this.minSeconds
)
private getSleepAmount(attempt: number): number {
if (this.minSeconds === 0) {
return 0
}
return Math.min(this.minSeconds * Math.pow(2, attempt - 1), this.maxSeconds)
}
private async sleep(seconds: number): Promise<void> {