jackwener/OpenCLI · error · CliError
REFRESH_FAILED
REFRESH_FAILED
Error message
${err?.error_description || `Token refresh failed (${res.status})`} What it means
refreshAccessToken exchanges the stored Spotify refresh token for a new access token using the client-credentials Basic auth header. When the token endpoint responds with a non-OK status, the CLI reads error_description from the JSON body (falling back to a generic message with the HTTP status) and throws a CliError with code REFRESH_FAILED. This means the stored credentials can no longer produce a valid access token.
Source
Thrown at clis/spotify/spotify.js:56
return null;
}
}
function saveTokens(tokens) {
mkdirSync(join(homedir(), '.opencli'), { recursive: true });
writeFileSync(TOKEN_FILE, JSON.stringify(tokens, null, 2));
}
async function refreshAccessToken(refreshToken) {
const res = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Basic ' + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64'),
},
body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken }),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new CliError('REFRESH_FAILED', err?.error_description || `Token refresh failed (${res.status})`);
}
const data = await res.json();
const tokens = {
access_token: data.access_token,
refresh_token: data.refresh_token || refreshToken,
expires_at: Date.now() + data.expires_in * 1000,
};
saveTokens(tokens);
return tokens.access_token;
}
async function getToken() {
const tokens = loadTokens();
if (!tokens)
throw new CliError('AUTH_REQUIRED', 'Not authenticated. Run: opencli spotify auth');
if (!tokens.access_token || !tokens.refresh_token || !(tokens.expires_at > 0)) {
throw new CliError('AUTH_CORRUPTED', 'Token file is corrupted. Run: opencli spotify auth');
}
if (Date.now() > tokens.expires_at - 60_000)View on GitHub (pinned to 49907e53dc)
Solutions
- Re-authenticate to obtain fresh tokens: run opencli spotify auth (deletes/overwrites the stale token file).
- Verify CLIENT_ID and CLIENT_SECRET match the Spotify app that originally issued the refresh token.
- Check the error_description in the message for invalid_grant vs invalid_client to decide which fix applies.
- Check Spotify status / retry if the status is 5xx (transient server error).
Example fix
// before: keep retrying with stale token
const tokens = await getToken(); // keeps failing with REFRESH_FAILED
// after: detect code and re-auth
catch (e) { if (e.code === 'REFRESH_FAILED') { await runAuthFlow(); } } Defensive patterns
Strategy: try-catch
Validate before calling
const tokens = loadTokens();
if (!tokens?.refresh_token) { console.error('No refresh token stored; run: opencli spotify auth'); }
if (!process.env.CLIENT_ID || !process.env.CLIENT_SECRET) { console.error('CLIENT_ID/CLIENT_SECRET not set'); } Try / catch
try {
const token = await getToken();
} catch (e) {
if (e.code === 'REFRESH_FAILED') {
if (/invalid_client/i.test(e.message)) console.error('Check CLIENT_ID/CLIENT_SECRET');
else if (/invalid_grant/i.test(e.message)) await runAuthFlow(); // token revoked/rotated
else if (e.message.includes('5')) retryWithBackoff();
} else throw e;
} Prevention
- Re-run opencli spotify auth periodically; refresh tokens can be revoked or rotated.
- Keep CLIENT_ID/CLIENT_SECRET stable and matching the app that issued the tokens.
- Detect e.code === 'REFRESH_FAILED' and trigger re-auth automatically.
When it happens
Trigger: POST to Spotify's token endpoint with grant_type=refresh_token returns 400/401 — typically invalid_grant (refresh token revoked or expired), invalid_client (wrong CLIENT_ID/CLIENT_SECRET), or a 5xx from Spotify.
Common situations: User revoked the app in their Spotify account; refresh token rotated and the old one stored on disk is stale; CLIENT_ID/CLIENT_SECRET env vars don't match the app that issued the token; clock or network issues; Spotify outage.
Related errors
- AUTH_REQUIRED
- AUTH_CORRUPTED
- API_ERROR
- CONFIG
- Failed to refresh Xiaoyuzhou credentials: ${getErrorMessage(
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/4582abde67a6e722.
Report an issue: GitHub.