decolua/9router · error
Token refresh failed: ${error}
Error message
Token refresh failed: ${error} What it means
Thrown by KiroService.refreshToken on the AWS SSO OIDC path (used when clientId and clientSecret are present, i.e. Builder ID / IDC device-flow accounts). The refresh call to oidc.<region>.amazonaws.com/token returned non-2xx and the AWS error body is included in the message. The stored refresh token could not be traded for a new access token.
Source
Thrown at src/lib/oauth/services/kiro.js:201
assertValidAwsRegion(safeRegion);
const endpoint = `https://oidc.${safeRegion}.amazonaws.com/token`;
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
clientId,
clientSecret,
refreshToken,
grantType: "refresh_token",
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Token refresh failed: ${error}`);
}
const data = await response.json();
return {
accessToken: data.accessToken,
refreshToken: data.refreshToken || refreshToken,
profileArn: data.profileArn,
expiresIn: data.expiresIn,
};
}
// Social auth refresh (Google/GitHub)
const response = await fetch(`${KIRO_AUTH_SERVICE}/refreshToken`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({View on GitHub (pinned to 90b52e06ff)
Solutions
- Treat this as an expired/revoked session: re-run the full device-code login (registerClient → startDeviceAuthorization → pollDeviceToken) and store the new tokens.
- If the body shows expired client secret, call registerClient again for fresh clientId/clientSecret before refreshing.
- Check the embedded AWS error: invalid_grant means re-auth is required; slow_down/throttling means retry with backoff.
- Verify the persisted region matches the one the token was issued in.
Example fix
// before: refreshing forever with a dead token
try { await svc.refreshToken(rt, { clientId, clientSecret, region }); } catch { /* retry loop */ }
// after: re-authenticate on invalid_grant
try {
await svc.refreshToken(rt, { clientId, clientSecret, region });
} catch (e) {
if (/invalid_grant|InvalidRefreshToken/i.test(e.message)) return deviceCodeLogin();
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
function canAttemptOidcRefresh(psd) {
return typeof psd?.clientId === 'string' && psd.clientId.length > 0 &&
typeof psd?.clientSecret === 'string' && psd.clientSecret.length > 0 &&
typeof psd?.refreshToken === 'string' && psd.refreshToken.startsWith('aorAAAAAG');
}
if (!canAttemptOidcRefresh(providerSpecificData)) throw new Error('Incomplete OIDC refresh credentials — re-authenticate'); Type guard
function isRefreshResult(r) { return typeof r?.accessToken === 'string' && r.accessToken.length > 0; } Try / catch
try {
return await svc.refreshToken(rt, { clientId, clientSecret, region });
} catch (e) {
if (/invalid_grant|revoked|expired/i.test(e.message)) {
markAccountNeedsReauth(accountId);
return null; // caller shows re-login UI
}
if (/throttl|SlowDown/i.test(e.message)) return retryWithBackoff();
throw e;
} Prevention
- Refresh proactively before expiresIn lapses instead of waiting for a 401.
- Distinguish invalid_grant (re-auth required) from throttling (retry) using the embedded error body.
- Store clientId/clientSecret/region with each account so the OIDC branch is always taken for Builder ID/IDC tokens.
- Watch clientSecretExpiresAt and re-run registerClient when it passes.
When it happens
Trigger: POST to oidc.<region>.amazonaws.com/token with grantType refresh_token returns !response.ok — typically invalid_grant because the refresh token was revoked/expired, an expired clientSecret, or a wrong region.
Common situations: User revoked the device authorization in AWS Builder ID settings; token idle past AWS's refresh-token lifetime; clientSecretExpiresAt passed; account moved between regions; AWS throttling.
Related errors
- Failed to register client: ${error}
- Failed to start device authorization: ${error}
- Failed to refresh credentials. Please re-authorize the conne
- OIDC token exchange failed (${res.status})
- Invalid region
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/167860245cd8d69d.
Report an issue: GitHub.