ruvnet/ruflo · error · ScopeConsentMismatchError
profile "${profile}" has scope(s) without matching local con
Error message
profile "${profile}" has scope(s) without matching local consent: ${scopes.join(', ')} — authenticated capability denied What it means
Thrown by getValidAccessToken (ScopeConsentMismatchError) when the profile's scopes include one or more domains for which no local consent record exists. Consent is a local gate: even with a valid token, scopes lacking consent are denied.
Source
Thrown at v3/@claude-flow/cli/src/auth/client.ts:246
* Returns an access token suitable for an authenticated call.
*
* Fast path: a process-memory token with more than one minute remaining.
* Slow path: load the profile's refresh token from the OS keychain, perform
* one refresh, persist a rotated refresh token BEFORE exposing the new access
* token, then update metadata and the process cache. Refresh is deliberately
* demand-driven: offline-safe commands such as plain `auth status` never call
* this function and therefore never create background traffic or retry loops.
*/
export async function getValidAccessToken(profileName = 'default'): Promise<string> {
const profile = getProfile(profileName);
if (!profile) throw new NotLoggedInError(profileName);
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) {View on GitHub (pinned to 6b01dc5a68)
Solutions
- Re-establish consent for each missing domain (e.g. ruflo auth consent <domain>), or re-login.
- Drop the unconsented scope from the profile if it is no longer needed.
Defensive patterns
Strategy: validation
Validate before calling
const missing = profile.scopes.filter(s => {
const d = domainForScope(s);
return d !== undefined && !hasConsent(d);
});
if (missing.length > 0) {
throw new Error(`Missing consent for: ${missing.join(', ')}`);
} Prevention
- Pair every scope grant with a consent write atomically.
- Audit consent records whenever a profile is imported or copied.
- Re-establish consent when scopes change server-side.
When it happens
Trigger: Profile exists but its scopes include a domain with no consent record: consent expired/revoked, a scope added to the profile after login, or a consent store reset.
Common situations: Consent store cleared; profile imported between machines without consent; scopes changed server-side after the original login.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
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/97c85287d2471608.
Report an issue: GitHub.