tinyhumansai/openhuman · critical
Login token invalid or expired
Error message
Login token invalid or expired
What it means
consumeLoginToken called openhuman.auth.consume_login_token and the response's result.jwtToken was missing/empty. The core consumed (or rejected) the one-time login token but produced no JWT, which the client reports as the token being invalid or expired — the dominant real-world cause.
Source
Thrown at app/src/services/api/authApi.ts:60
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;
if (!jwtToken) {
throw new Error('Login token invalid or expired');
}
return jwtToken;
}
View on GitHub (pinned to a221052e0d)
Solutions
- Restart the login flow to mint a fresh token (request a new magic link / re-auth via Telegram)
- Ensure the deep-link/token handler is idempotent-guarded so the same token isn't consumed twice by competing listeners
- Check core logs for the consume_login_token outcome to distinguish 'used' vs 'expired' vs 'unknown'
- If it persists for every fresh token, check version skew between frontend and core envelope shapes
Example fix
// before
const jwt = await consumeLoginToken(token);
setSession(jwt);
// after
let jwt: string;
try {
jwt = await consumeLoginToken(token);
} catch {
showLoginError('This login link is invalid or expired. Request a new one.');
navigateToLogin();
return;
}
setSession(jwt); Defensive patterns
Strategy: try-catch
Type guard
const hasJwt = (r: unknown): r is { result: { jwtToken: string } } =>
typeof (r as { result?: { jwtToken?: unknown } })?.result?.jwtToken === 'string' &&
((r as { result: { jwtToken: string } }).result.jwtToken.length > 0); Try / catch
try { const jwt = await consumeLoginToken(token); setSession(jwt); }
catch (e) {
if (String((e as Error).message).includes('invalid or expired')) {
showLoginError('This link has expired. Request a new login link.'); navigateToLogin();
} else throw e;
} Prevention
- Mint a fresh token per login attempt instead of reusing deep links
- Guard deep-link handlers so the same token is consumed exactly once
- Complete the token exchange promptly — treat login links as short-lived
When it happens
Trigger: Completing Telegram/OAuth login with a token already used once (one-time consumption), a token past its TTL, a truncated/copy-mangled token string, or a core version whose envelope differs so jwtToken is read as undefined.
Common situations: User clicks an old login link (reopening a stale email/Telegram message); double-processing the same deep link by two handlers; clock skew or long delay between link issue and click; dev flow against a reset database where the token no longer exists.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Invalid ${paramName}: ${String(value)}. Type must be an inte
- Invalid ${paramName}: '${value}'. Must be a valid integer ID
- OpenHuman uses the session JWT — keys are not configurable h
- Model test RPC returned no result for ${workload} via ${prov
- Failed to send magic link (${response.status})
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/9b222b18f825ccd5.
Report an issue: GitHub.