{"record":{"id":"37924aa2c4174250","repo":"ruvnet/ruflo","slug":"profile-profile-has-no-persisted-refresh-toke","errorCode":null,"errorMessage":"profile \"${profile}\" has no persisted refresh token and its in-memory access token is absent or expiring — run: ruflo auth login --profile ${profile}","messagePattern":"profile \"(.+?)\" has no persisted refresh token and its in-memory access token is absent or expiring — run: ruflo auth login --profile (.+?)","errorType":"exception","errorClass":"SessionOnlyExpiredError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/auth/client.ts","lineNumber":251,"sourceCode":" * token, then update metadata and the process cache. Refresh is deliberately\n * demand-driven: offline-safe commands such as plain `auth status` never call\n * this function and therefore never create background traffic or retry loops.\n */\nexport async function getValidAccessToken(profileName = 'default'): Promise<string> {\n  const profile = getProfile(profileName);\n  if (!profile) throw new NotLoggedInError(profileName);\n\n  const scopesWithoutConsent = profile.scopes.filter((scope) => {\n    const domain = domainForScope(scope);\n    return domain !== undefined && !hasConsent(domain);\n  });\n  if (scopesWithoutConsent.length > 0) {\n    throw new ScopeConsentMismatchError(profileName, scopesWithoutConsent);\n  }\n\n  const cached = getSessionToken(profileName, ACCESS_TOKEN_REFRESH_WINDOW_MS);\n  if (cached) return cached;\n  if (!profile.keychainRef) throw new SessionOnlyExpiredError(profileName);\n\n  const sec = await loadSecurityOAuth();\n  const keychain = await sec.createKeychainAdapter();\n  const refreshTokenValue = await keychain.getSecret(KEYCHAIN_SERVICE, profile.keychainRef);\n  if (!refreshTokenValue) throw new SessionOnlyExpiredError(profileName);\n\n  const refreshed = await refreshAccessToken(refreshTokenValue);\n  if (!refreshed.access_token) throw new Error('Cognitum refresh response did not contain an access token');\n\n  // Cognitum rotates refresh tokens with reuse detection. Commit the rotated\n  // credential first; if this write fails, do not publish/cache the access\n  // token and do not retry the already-spent old refresh token here.\n  if (refreshed.refresh_token) {\n    await keychain.setSecret(KEYCHAIN_SERVICE, profile.keychainRef, refreshed.refresh_token);\n  }\n\n  const expiresAtMs = Date.now() + Math.max(0, refreshed.expires_in ?? 0) * 1000;\n  setSessionToken(profileName, refreshed.access_token, expiresAtMs);","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/auth/client.ts#L233-L269","documentation":"Thrown at line 251 (SessionOnlyExpiredError) when profile.keychainRef is absent. The profile was session-only (e.g. --token-stdin login) and never persisted a refresh token to the OS keychain; with the in-memory access token expired or expiring, no refresh is possible.","triggerScenarios":"A session-only login (no keychainRef) whose in-memory token is absent or within the refresh window, so getValidAccessToken cannot refresh and has nothing to fall back on.","commonSituations":"Logged in via --token-stdin in a previous process; long-running process whose memory token expired; CI without keychain access.","solutions":["Re-login with a persistent method (interactive/manual PKCE) so a refresh token is stored in the keychain.","Re-inject a fresh access token via --token-stdin for the next session."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if (!profile.keychainRef) {\n  throw new Error('Session-only profile has no persisted refresh token; run a persistent login');\n}","typeGuard":"function isPersistentProfile(p: { keychainRef?: string }): boolean {\n  return typeof p.keychainRef === 'string' && p.keychainRef.length > 0;\n}","tryCatchPattern":null,"preventionTips":["For long-lived processes, use keychain-backed login rather than --token-stdin.","Detect session-only profiles early and warn the user.","Document which login methods are persistent vs session-only."],"tags":["oauth","session","keychain","auth","token-expiry"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}