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

  1. 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
  2. If it's consent denial, simply re-run `ruflo auth login` and approve
  3. 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)
  4. 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

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


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/907b143c7e696263. Report an issue: GitHub.