#!/usr/bin/env bun import * as core from "@actions/core"; type RetryOptions = { maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; backoffFactor?: number; }; async function retryWithBackoff( operation: () => Promise, options: RetryOptions = {}, ): Promise { const { maxAttempts = 3, initialDelayMs = 5000, maxDelayMs = 20000, backoffFactor = 2, } = options; let delayMs = initialDelayMs; let lastError: Error | undefined; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { console.log(`Attempt ${attempt} of ${maxAttempts}...`); return await operation(); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); console.error(`Attempt ${attempt} failed:`, lastError.message); if (attempt < maxAttempts) { console.log(`Retrying in ${delayMs / 1000} seconds...`); await new Promise((resolve) => setTimeout(resolve, delayMs)); delayMs = Math.min(delayMs * backoffFactor, maxDelayMs); } } } console.error(`Operation failed after ${maxAttempts} attempts`); throw lastError; } async function getOidcToken(): Promise { try { const oidcToken = await core.getIDToken("claude-code-github-action"); return oidcToken; } catch (error) { console.error("Failed to get OIDC token:", error); throw new Error( "Could not fetch an OIDC token. Did you remember to add `id-token: write` to your workflow permissions?", ); } } async function exchangeForAppToken(oidcToken: string): Promise { const response = await fetch( "https://api.anthropic.com/api/github/github-app-token-exchange", { method: "POST", headers: { Authorization: `Bearer ${oidcToken}`, }, }, ); if (!response.ok) { const responseJson = (await response.json()) as { error?: { message?: string; }; }; console.error( `App token exchange failed: ${response.status} ${response.statusText} - ${responseJson?.error?.message ?? "Unknown error"}`, ); throw new Error(`${responseJson?.error?.message ?? "Unknown error"}`); } const appTokenData = (await response.json()) as { token?: string; app_token?: string; }; const appToken = appTokenData.token || appTokenData.app_token; if (!appToken) { throw new Error("App token not found in response"); } return appToken; } export async function setupGitHubToken(): Promise { try { // Check if GitHub token was provided as override const providedToken = process.env.OVERRIDE_GITHUB_TOKEN; if (providedToken) { console.log("Using provided GITHUB_TOKEN for authentication"); core.setOutput("GITHUB_TOKEN", providedToken); return providedToken; } console.log("Requesting OIDC token..."); const oidcToken = await retryWithBackoff(() => getOidcToken()); console.log("OIDC token successfully obtained"); console.log("Exchanging OIDC token for app token..."); const appToken = await retryWithBackoff(() => exchangeForAppToken(oidcToken), ); console.log("App token successfully obtained"); console.log("Using GITHUB_TOKEN from OIDC"); core.setOutput("GITHUB_TOKEN", appToken); return appToken; } catch (error) { core.setFailed( `Failed to setup GitHub token: ${error}.\n\nIf you instead wish to use this action with a custom GitHub token or custom GitHub app, provide a \`github_token\` in the \`uses\` section of the app in your workflow yml file.`, ); process.exit(1); } }