ruvnet/ruflo · error · Error
Cognitum refresh response did not contain an access token
Error message
Cognitum refresh response did not contain an access token
What it means
Thrown by getValidAccessToken() after the refresh-token HTTP round-trip to Cognitum's token endpoint succeeded (resolved, not rejected) but the parsed response body has no access_token field. This is a service-contract violation, distinct from network-unreachable failures (those are classified separately inside refreshAccessToken). The guard prevents caching or publishing a useless empty token.
Source
Thrown at v3/@claude-flow/cli/src/auth/client.ts:259
const scopesWithoutConsent = profile.scopes.filter((scope) => {
const domain = domainForScope(scope);
return domain !== undefined && !hasConsent(domain);
});
if (scopesWithoutConsent.length > 0) {
throw new ScopeConsentMismatchError(profileName, scopesWithoutConsent);
}
const cached = getSessionToken(profileName, ACCESS_TOKEN_REFRESH_WINDOW_MS);
if (cached) return cached;
if (!profile.keychainRef) throw new SessionOnlyExpiredError(profileName);
const sec = await loadSecurityOAuth();
const keychain = await sec.createKeychainAdapter();
const refreshTokenValue = await keychain.getSecret(KEYCHAIN_SERVICE, profile.keychainRef);
if (!refreshTokenValue) throw new SessionOnlyExpiredError(profileName);
const refreshed = await refreshAccessToken(refreshTokenValue);
if (!refreshed.access_token) throw new Error('Cognitum refresh response did not contain an access token');
// Cognitum rotates refresh tokens with reuse detection. Commit the rotated
// credential first; if this write fails, do not publish/cache the access
// token and do not retry the already-spent old refresh token here.
if (refreshed.refresh_token) {
await keychain.setSecret(KEYCHAIN_SERVICE, profile.keychainRef, refreshed.refresh_token);
}
const expiresAtMs = Date.now() + Math.max(0, refreshed.expires_in ?? 0) * 1000;
setSessionToken(profileName, refreshed.access_token, expiresAtMs);
setProfile(profileName, {
...profile,
accountId: refreshed.account_email ?? profile.accountId,
accessTokenExpiresAt: new Date(expiresAtMs).toISOString(),
linkedAt: new Date().toISOString(),
});
return refreshed.access_token;
}View on GitHub (pinned to 6b01dc5a68)
Solutions
- Re-run `ruflo auth login --profile <name>` to obtain a fresh token pair — persistent service-contract drift cannot be fixed by retrying the spent refresh token
- Check @claude-flow/security release notes for a token-response parser update and upgrade if a newer version exists
- Inspect the raw HTTP response from auth.cognitum.one (enable security package debug logging) to confirm whether the body is empty or shaped differently
- If behind a proxy, verify it passes JSON response bodies through unmodified
Defensive patterns
Strategy: try-catch
Try / catch
try {
const token = await getValidAccessToken(profile);
} catch (e) {
if (e instanceof Error && /did not contain an access token/.test(e.message)) {
// The refresh token was spent but the service returned a malformed body.
// Do NOT retry getValidAccessToken — the rotated refresh token is already
// committed. Prompt the user to re-authenticate from scratch.
console.error('Auth service returned a malformed token response. Run: ruflo auth login --profile ' + profile);
process.exit(1);
}
throw e;
} Prevention
- Keep @claude-flow/security updated so the token-response parser matches the current Cognitum contract
- Monitor auth service changelog for breaking changes to the token endpoint response shape
- In automated/CI environments, prefer --token-stdin with a freshly minted token over relying on refresh
When it happens
Trigger: Any authenticated ruflo command runs after the in-memory session token expired (less than 60s left) AND a refresh token exists in the OS keychain AND refreshAccessToken() resolves but refreshed.access_token is falsy (undefined, null, or empty string).
Common situations: Cognitum auth service shipped a breaking response-shape change; a corporate proxy rewrote or stripped the JSON body; the security package's refreshToken() parsed a non-OAuth response (HTML error page served with HTTP 200); clock skew or a misconfigured endpoint returns an error payload inside a 200.
Related errors
- state mismatch — the OAuth callback did not match the reques
- login cancelled: no code was entered
- --token-stdin: no input received on stdin
- --token-stdin expects a single JSON object: {"access_token",
- --token-stdin: JSON is missing required field "access_token"
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/3d2bb0ec899d26ec.
Report an issue: GitHub.