Skip to content

Commit 6c9193b

Browse files
committed
fix: token default / download limits and retries
Signed-off-by: Sam Gammon <sam@elide.ventures>
1 parent 2c4a4af commit 6c9193b

5 files changed

Lines changed: 138 additions & 69 deletions

File tree

__tests__/releases-download.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,37 @@ describe('resolveLatestVersion', () => {
8383
expect(getOctokitMock).toHaveBeenCalledWith('ghp_test_token')
8484
expect(result.userProvided).toBe(true)
8585
})
86+
87+
it('should retry on transient failure and succeed', async () => {
88+
requestMock
89+
.mockRejectedValueOnce(new Error('rate limit exceeded'))
90+
.mockResolvedValueOnce({
91+
data: { tag_name: '2.0.0', name: 'Elide 2.0.0' }
92+
})
93+
const result = await resolveLatestVersion()
94+
expect(result.tag_name).toBe('2.0.0')
95+
expect(requestMock).toHaveBeenCalledTimes(2)
96+
expect(warningMock).toHaveBeenCalledWith(
97+
expect.stringContaining('Retrying')
98+
)
99+
})
100+
101+
it('should throw after exhausting retries', async () => {
102+
requestMock.mockRejectedValue(new Error('quota exhausted'))
103+
await expect(resolveLatestVersion()).rejects.toThrow('quota exhausted')
104+
expect(requestMock).toHaveBeenCalledTimes(3)
105+
expect(errorMock).toHaveBeenCalledWith(
106+
expect.stringContaining('rate limit'),
107+
expect.objectContaining({ title: 'Rate Limited' })
108+
)
109+
})
110+
111+
it('should warn when no token is provided', async () => {
112+
await resolveLatestVersion()
113+
expect(warningMock).toHaveBeenCalledWith(
114+
expect.stringContaining('No GitHub token provided')
115+
)
116+
})
86117
})
87118

88119
describe('downloadRelease', () => {

action.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ inputs:
6363
token:
6464
description: 'GitHub Token'
6565
required: false
66+
default: ${{ github.token }}
6667

6768
outputs:
6869
path:

dist/index.js

Lines changed: 49 additions & 49 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/releases.ts

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -267,33 +267,70 @@ async function unpackRelease(
267267
return target
268268
}
269269

270+
const MAX_RETRIES = 3
271+
const RETRY_DELAY_MS = 1000
272+
270273
/**
271-
* Fetch the latest Elide release from GitHub.
274+
* Fetch the latest Elide release from GitHub, with retry on transient failures.
272275
*
273276
* @param token GitHub token active for this workflow step.
274277
*/
275278
export async function resolveLatestVersion(
276279
token?: string
277280
): Promise<ElideVersionInfo> {
281+
if (!token) {
282+
core.warning(
283+
'No GitHub token provided. API requests may be rate-limited. ' +
284+
'Set the `token` input or ensure GITHUB_TOKEN is available.'
285+
)
286+
}
278287
const octokit = token ? github.getOctokit(token) : new Octokit({})
279-
const latest = await octokit.request(
280-
'GET /repos/{owner}/{repo}/releases/latest',
281-
{
282-
owner: 'elide-dev',
283-
repo: 'elide',
284-
headers: GITHUB_DEFAULT_HEADERS
285-
}
286-
)
287288

288-
if (!latest) {
289-
throw new Error('Failed to fetch the latest Elide version')
290-
}
291-
const name = latest.data?.name || undefined
292-
return {
293-
name,
294-
tag_name: latest.data.tag_name,
295-
userProvided: !!token
289+
let lastError: Error | undefined
290+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
291+
try {
292+
const latest = await octokit.request(
293+
'GET /repos/{owner}/{repo}/releases/latest',
294+
{
295+
owner: 'elide-dev',
296+
repo: 'elide',
297+
headers: GITHUB_DEFAULT_HEADERS
298+
}
299+
)
300+
301+
if (!latest) {
302+
throw new Error('Failed to fetch the latest Elide version')
303+
}
304+
const name = latest.data?.name || undefined
305+
return {
306+
name,
307+
tag_name: latest.data.tag_name,
308+
userProvided: !!token
309+
}
310+
} catch (err) {
311+
lastError = err instanceof Error ? err : new Error(String(err))
312+
const isRateLimit =
313+
lastError.message.includes('rate limit') ||
314+
lastError.message.includes('quota exhausted') ||
315+
lastError.message.includes('403') ||
316+
lastError.message.includes('429')
317+
318+
if (attempt < MAX_RETRIES) {
319+
const delay = RETRY_DELAY_MS * attempt
320+
core.warning(
321+
`GitHub API request failed (attempt ${attempt}/${MAX_RETRIES}): ${lastError.message}. Retrying in ${delay}ms...`
322+
)
323+
await new Promise(resolve => setTimeout(resolve, delay))
324+
} else if (isRateLimit) {
325+
core.error(
326+
'GitHub API rate limit exhausted. Provide a token via the `token` input to increase the limit.',
327+
{ title: 'Rate Limited' }
328+
)
329+
}
330+
}
296331
}
332+
333+
throw lastError!
297334
}
298335

299336
/**

0 commit comments

Comments
 (0)