{"record":{"id":"4582abde67a6e722","repo":"jackwener/OpenCLI","slug":"refresh-failed","errorCode":"REFRESH_FAILED","errorMessage":"${err?.error_description || `Token refresh failed (${res.status})`}","messagePattern":"\\$\\{err\\?\\.error_description \\|\\| `Token refresh failed \\(\\$\\{res\\.status\\}\\)`\\}","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/spotify/spotify.js","lineNumber":56,"sourceCode":"        return null;\n    }\n}\nfunction saveTokens(tokens) {\n    mkdirSync(join(homedir(), '.opencli'), { recursive: true });\n    writeFileSync(TOKEN_FILE, JSON.stringify(tokens, null, 2));\n}\nasync function refreshAccessToken(refreshToken) {\n    const res = await fetch('https://accounts.spotify.com/api/token', {\n        method: 'POST',\n        headers: {\n            'Content-Type': 'application/x-www-form-urlencoded',\n            Authorization: 'Basic ' + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64'),\n        },\n        body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken }),\n    });\n    if (!res.ok) {\n        const err = await res.json().catch(() => ({}));\n        throw new CliError('REFRESH_FAILED', err?.error_description || `Token refresh failed (${res.status})`);\n    }\n    const data = await res.json();\n    const tokens = {\n        access_token: data.access_token,\n        refresh_token: data.refresh_token || refreshToken,\n        expires_at: Date.now() + data.expires_in * 1000,\n    };\n    saveTokens(tokens);\n    return tokens.access_token;\n}\nasync function getToken() {\n    const tokens = loadTokens();\n    if (!tokens)\n        throw new CliError('AUTH_REQUIRED', 'Not authenticated. Run: opencli spotify auth');\n    if (!tokens.access_token || !tokens.refresh_token || !(tokens.expires_at > 0)) {\n        throw new CliError('AUTH_CORRUPTED', 'Token file is corrupted. Run: opencli spotify auth');\n    }\n    if (Date.now() > tokens.expires_at - 60_000)","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/spotify/spotify.js#L38-L74","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before: keep retrying with stale token\nconst tokens = await getToken(); // keeps failing with REFRESH_FAILED\n// after: detect code and re-auth\ncatch (e) { if (e.code === 'REFRESH_FAILED') { await runAuthFlow(); } }","handlingStrategy":"try-catch","validationCode":"const tokens = loadTokens();\nif (!tokens?.refresh_token) { console.error('No refresh token stored; run: opencli spotify auth'); }\nif (!process.env.CLIENT_ID || !process.env.CLIENT_SECRET) { console.error('CLIENT_ID/CLIENT_SECRET not set'); }","typeGuard":null,"tryCatchPattern":"try {\n  const token = await getToken();\n} catch (e) {\n  if (e.code === 'REFRESH_FAILED') {\n    if (/invalid_client/i.test(e.message)) console.error('Check CLIENT_ID/CLIENT_SECRET');\n    else if (/invalid_grant/i.test(e.message)) await runAuthFlow(); // token revoked/rotated\n    else if (e.message.includes('5')) retryWithBackoff();\n  } else throw e;\n}","preventionTips":["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."],"tags":["oauth","spotify","token-refresh","network"],"backgroundTag":"oauth-refresh-token-invalid","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}