ruvnet/ruflo · error · LoginDeniedError
authorization was denied or failed: ${detail}
Error message
authorization was denied or failed: ${detail} What it means
LoginDeniedError means the OAuth authorization server (or the callback) reported failure rather than success: validateCallback saw an explicit error parameter, or a callback with no authorization code at all. The detail string is the server's error value (e.g. access_denied, server_error) or 'no authorization code received'. This is the server/user saying 'no' — distinct from StateMismatchError, which is tampering/mismatch.
Source
Thrown at v3/@claude-flow/cli/src/auth/client.ts:133
}
/** Browser-based loopback PKCE login — the ADR-306 default for an interactive desktop. */
export async function browserLogin(print: (line: string) => void): Promise<LoginResult> {
const sec = await loadSecurityOAuth();
const server = await sec.CallbackServer.bind();
const pkce = sec.generatePkce();
const url = sec.authorizeUrl(server.redirectUri, pkce.state, pkce.codeChallenge);
print('Opening your browser to sign in to Cognitum...');
print(`If it doesn't open automatically, visit:\n\n ${url}\n`);
await sec.openBrowser(url).catch(() => {}); // best-effort — the URL above is always the fallback
print('Waiting for you to finish signing in...');
const result = await server.awaitCallback();
const validated = validateCallback(result.error, result.code, result.state, pkce.state);
if (!validated.ok) {
if (validated.reason === 'state-mismatch') throw new StateMismatchError();
throw new LoginDeniedError(validated.detail ?? 'unknown');
}
const tokens = await sec.exchangeCode(validated.code, pkce.codeVerifier, server.redirectUri);
return { tokens, method: 'pkce' };
}
/** Headless fallback: prints the authorize URL with the OOB redirect, prompts for the pasted code. */
export async function manualLogin(
print: (line: string) => void,
input: NodeJS.ReadableStream = process.stdin,
): Promise<LoginResult> {
const sec = await loadSecurityOAuth();
print('Browser-based callback unavailable (SSH/container detected, or --no-browser).\n');
const pkce = sec.generatePkce();
const url = sec.authorizeUrl(sec.OOB_REDIRECT_URI, pkce.state, pkce.codeChallenge);
print(`Open this URL in a browser and authorize:\n\n ${url}\n`);
View on GitHub (pinned to fa13ee4ad6)
Solutions
- Read the detail: access_denied = user action (re-run and approve); server_error/temporarily_unavailable = retry later; invalid_request = client/redirect misconfiguration to report or fix
- If it's consent denial, simply re-run `ruflo auth login` and approve
- For persistent invalid_request, verify the CLI version matches the currently supported auth surface (the code targets auth.cognitum.one; stale clients may send unsupported params)
- Fallback for constrained environments: `ruflo auth login --token-stdin` with an out-of-band token
Example fix
// before — any auth failure crashes the command
await browserLogin(print);
// after — branch on the typed error class
try {
await browserLogin(print);
} catch (e) {
if (e instanceof LoginDeniedError) {
print(`Login failed: ${e.message}`);
if (!e.message.includes('access_denied')) print('The auth server may be down — try again later.');
process.exitCode = 1;
} else throw e;
} Defensive patterns
Strategy: try-catch
Try / catch
import { LoginDeniedError } from './auth/client.js';
try { await browserLogin(print); }
catch (e) {
if (e instanceof LoginDeniedError) {
const detail = e.message.replace('authorization was denied or failed: ', '');
if (detail === 'access_denied') { /* user refused: prompt to re-run */ }
else { /* server error: schedule retry, surface detail */ }
}
throw e;
} Prevention
- Surface the embedded detail string — it distinguishes user denial from server failure
- Update the CLI when the auth surface changes so request params stay valid
- For automation, prefer --token-stdin over the browser flow
When it happens
Trigger: browserLogin()/manualLogin flows where the user clicks 'Deny' on the consent screen (error=access_denied), the IdP returns server_error/temporarily_unavailable, or the callback fires with ?error=... or with neither code nor error (detail: 'no authorization code received').
Common situations: User cancels consent; the Cognitum auth surface is down or misconfigured (client_id/redirect_uri mismatch surfaces as invalid_request); corporate SSO policies auto-denying unknown apps; callback ports blocked so an empty request hits the loopback server.
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@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/907b143c7e696263.
Report an issue: GitHub.