actualbudget/actual · warning

No token received.

Error message

No token received.

What it means

In the desktop (Electron) app's OAuth callback, an inline HTTP server listens on 127.0.0.1 for the provider's redirect. If the request arrives without the expected token query parameter (the handler closes the server when a token is received, otherwise it responds), the server replies 400 with the plain-text body 'No token received.' and the OAuth flow fails.

Source

Thrown at packages/desktop-electron/index.ts:130

          );
        } else {
          void clientWin.loadURL(`app://actual/openid-cb?token=${code}`);
        }

        // Respond to the browser
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.end('OpenID login successful! You can close this tab.');

        // Clean up the server after receiving the code. Wait for the listener
        // to fully release port 3010 before clearing the reference, otherwise a
        // subsequent start-oauth-server request could try to bind the port
        // while this listener is still shutting down.
        await new Promise<void>(closeResolve => {
          server.close(() => closeResolve());
        });
        oAuthServer = null;
      } else {
        res.writeHead(400, { 'Content-Type': 'text/plain' });
        res.end('No token received.');
      }
    });

    server.listen(port, '127.0.0.1', () => {
      logMessage('info', `OAuth server started on port: ${port}`);
      resolve({ url: `http://localhost:${port}`, server });
    });
  });
};

if (isDev) {
  process.traceProcessWarnings = true;
}

async function loadGlobalPrefs() {
  let state: GlobalPrefsJson = {};
  try {

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Retry the sign-in flow and complete authorization at the provider instead of cancelling.
  2. Check the provider's redirect for an error query parameter (access_denied etc.) and address the underlying cause (app not approved, account blocked).
  3. Verify the OAuth redirect URI / port configured in the app matches what the provider is allowed to redirect to.
  4. Check for browser extensions or proxies that alter the redirect URL and drop the token parameter.

Example fix

// before: provider redirect missing token
http://127.0.0.1:3999/oauth?state=xyz
// after: redirect must include the token
http://127.0.0.1:3999/oauth?token=abc123&state=xyz
Defensive patterns

Strategy: validation

Validate before calling

const parsed = new URL(redirectUrl, 'http://127.0.0.1');
if (!parsed.searchParams.get('token')) {
  const err = parsed.searchParams.get('error');
  throw new Error(`OAuth redirect missing token${err ? ` (provider error: ${err})` : ''}`);
}

Type guard

function hasOAuthToken(u: URL): boolean {
  return typeof u.searchParams.get('token') === 'string' && u.searchParams.get('token')!.length > 0;
}

Try / catch

try {
  const token = await startOAuthFlow(); // resolves with token from local callback server
} catch (e) {
  if (e.message.includes('No token received')) {
    logger.warn('OAuth login cancelled or redirect lacked a token; prompting user to retry');
  }
}

Prevention

When it happens

Trigger: The OAuth identity provider redirects back to http://127.0.0.1:<port> without a token (or code-derived token) query parameter — e.g. the user cancels login at the provider, the provider sends an error response, or the redirect URL was modified/corrupted so the token param is missing.

Common situations: User cancels the OAuth consent screen; provider redirects with error=access_denied instead of a token; a mismatch between the configured redirect port and the app's listening port causing a malformed/foreign request to hit the callback; browser extensions stripping query params.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/a309f5c6950a07c1. Report an issue: GitHub.