tinyhumansai/openhuman · error

Request timed out. Please try again.

Error message

Request timed out. Please try again.

What it means

The magic-link send request exceeded its client-side timeout: window.setTimeout aborted the fetch via AbortController, the AbortError was caught in the catch block, and it is rethrown as this friendly timeout message. The request never completed — the backend may or may not have sent the email.

Source

Thrown at app/src/services/api/authApi.ts:40

  try {
    const versionHeaders = await getClientVersionHeaders();
    const response = await fetch(`${backendUrl}/auth/email/send-link`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', ...versionHeaders },
      body: JSON.stringify({ email, frontendRedirectUri }),
      signal: controller.signal,
    });
    if (!response.ok) {
      const body = (await response.json().catch(() => ({}))) as { error?: string };
      throw new Error(body.error ?? `Failed to send magic link (${response.status})`);
    }
  } catch (error) {
    if (
      (error instanceof DOMException && error.name === 'AbortError') ||
      (error instanceof Error && error.name === 'AbortError')
    ) {
      throw new Error('Request timed out. Please try again.');
    }
    throw error;
  } finally {
    window.clearTimeout(timeoutId);
  }
}

/**
 * Consume a verified login token and return the JWT.
 * Works for both Telegram and OAuth login tokens.
 * POST /telegram/login-tokens/:token/consume (no auth required)
 */
export async function consumeLoginToken(loginToken: string): Promise<string> {
  const response = await callCoreRpc<{ result: { jwtToken: string } }>({
    method: 'openhuman.auth.consume_login_token',
    params: { loginToken },
  });
  const jwtToken = response.result?.jwtToken;

View on GitHub (pinned to a221052e0d)

Solutions

  1. Retry the send (the common case is transient) — but warn that a duplicate email may arrive if the first eventually succeeded
  2. Verify network/backend reachability, then retry once the service responds to /health
  3. Increase the timeout constant if the backend legitimately needs longer (cold starts)
  4. Debounce the send button so impatience-driven duplicates don't stack timeouts

Example fix

// before
await sendMagicLink(email);

// after
try {
  await sendMagicLink(email);
} catch (e) {
  if (String(e.message).includes('timed out')) {
    showNotice('The request timed out. Check your connection and try again.');
    return;
  }
  throw e;
}
Defensive patterns

Strategy: retry

Try / catch

const withTimeoutRetry = async (fn: () => Promise<void>, tries = 2) => {
  for (let i = 0; i < tries; i++) {
    try { return await fn(); }
    catch (e) {
      if (!String((e as Error).message).includes('timed out') || i === tries - 1) throw e;
      await new Promise(r => setTimeout(r, 1000 * (i + 1)));
    }
  }
};
await withTimeoutRetry(() => sendMagicLink(email));

Prevention

When it happens

Trigger: Slow or unreachable backend (fetch pending past timeoutMs); dev machine offline/VPN-blocked; backend cold start taking longer than the configured timeout. Any AbortError named signal from this controller maps here.

Common situations: Local backend still compiling when the login screen is used; network flakiness on mobile; an aggressive timeout constant after a latency regression; suspend/resume of the machine mid-request.

Understand the failure class

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/c143cea1a516b161. Report an issue: GitHub.