ruvnet/ruflo · error

Cognitum auth service returned an unexpected response

Error message

Cognitum auth service returned an unexpected response: ${e.message}

What it means

refreshAccessToken() rethrows an OAuthError from the security package whose code is NOT 'network' — i.e. the Cognitum auth service was reachable but the refresh failed for a protocol/server reason (invalid_grant, server error, malformed token response). The original message from @claude-flow/security is embedded verbatim.

Solutions

  1. Re-authenticate: run `ruflo auth login --profile <profile>` to obtain fresh tokens (most invalid_grant cases)
  2. If multiple machines share credentials, stop doing that — rotation with reuse detection will keep invalidating the loser
  3. Check the embedded e.message: 'invalid_grant' means re-login; HTTP 5xx means wait and retry later
  4. If it persists after a fresh login, check the auth service status and any proxy between you and auth.cognitum.one
Defensive patterns

Strategy: try-catch

Try / catch

try {
  token = await getValidAccessToken(profile);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Cognitum auth service returned an unexpected response')) {
    // e.message suffix is the server's OAuthError text: invalid_grant => re-login, 5xx => retry later
    if (e.message.includes('invalid_grant')) await promptRelogin(profile);
    else await retryLater();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling getValidAccessToken() or refreshAccessToken() when: the persisted refresh token was revoked or expired server-side (invalid_grant); the refresh token was already spent (Cognitum rotates tokens with reuse detection, so a replayed/stale token fails); the server returned 5xx; or the token endpoint responded with an unexpected body/status.

Common situations: Refresh token rotated on another machine sharing the same profile store, so this machine's copy is stale; server-side revocation of old tokens; auth service deployed a breaking change; clock skew or malformed responses via an intercepting proxy.

Related errors


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

Appendix: source

Thrown at v3/@claude-flow/cli/src/auth/client.ts:221

 * Refreshes an access token. Classifies failure into network-unreachable
 * vs. a reachable-but-erroring server so callers can print an honest
 * message instead of collapsing both into "offline" (ADR-308 failure
 * policy: local ruflo functionality is never affected by auth being
 * unavailable, but the diagnostic should say WHY it's unavailable).
 */
export async function refreshAccessToken(refreshTokenValue: string): Promise<OAuthTokenResponse> {
  const sec = await loadSecurityOAuth();
  try {
    return await sec.refreshToken(refreshTokenValue);
  } catch (e) {
    if (e instanceof sec.OAuthError) {
      if (e.code === 'network') {
        throw new Error(
          'Could not reach the Cognitum auth service. ruflo core functionality is unaffected — ' +
            'sign-in is not required for local use.',
        );
      }
      throw new Error(`Cognitum auth service returned an unexpected response: ${e.message}`);
    }
    throw e;
  }
}

/**
 * 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);

View on GitHub (pinned to fa13ee4ad6)