actualbudget/actual · error
login: User token not set
Error message
login: User token not set
What it means
After a sign-in exchange with the sync server, signIn expects the server response to contain a session token; if res.token is missing the response shape is unexpected and it throws rather than storing an undefined token.
Source
Thrown at packages/loot-core/src/server/auth/app.ts:278
throw new Error('No sync server configured.');
}
res = await post(serverConfig.SIGNUP_SERVER + '/login', loginInfo);
} catch (err) {
if (err instanceof PostError) {
return {
error: err.reason || 'network-failure',
};
}
throw err;
}
if (res.returnUrl) {
return { redirectUrl: res.returnUrl };
}
if (!res.token) {
throw new Error('login: User token not set');
}
await asyncStorage.setItem('user-token', res.token);
return {};
}
async function signOut() {
encryption.unloadAllKeys();
await asyncStorage.multiRemove([
'user-token',
'encrypt-keys',
'lastBudget',
'readOnly',
]);
return 'ok';
}
async function setToken({ token }: { token: string }) {View on GitHub (pinned to d4334cb6e6)
Solutions
- Check that the sync server and client versions are compatible; upgrade the sync server.
- Verify the endpoint is a real Actual sync server (SIGNUP_SERVER) and not a proxy masking errors.
- Inspect the sign-in response for an error field (PostError reasons like 'invalid-password') and surface it before assuming success.
Example fix
// before
const res = await send(signupServer + '/login', { username, password });
if (res.token) ... // server version mismatch: no token, throws later
// after
if (res.token == null) {
throw new Error('Sign-in failed: ' + (res.reason || 'no token returned'));
} Defensive patterns
Strategy: try-catch
Validate before calling
function expectsToken(res) { return res && typeof res.token === 'string' && res.token.length > 0; }
if (!expectsToken(signInResponse)) throw new Error('Sign-in response missing token; check server version/compatibility'); Type guard
function hasToken(res: unknown): res is { token: string } {
return typeof res === 'object' && res !== null && 'token' in res && typeof (res as { token?: unknown }).token === 'string';
} Try / catch
try {
await app.signIn({ password, useOpenId });
} catch (e) {
if (e.message.includes('User token not set')) {
console.error('Server returned no token — verify sync-server version matches the client and the endpoint is a real Actual server.');
} else throw e;
} Prevention
- Keep sync-server and client versions in lockstep
- Do not put a proxy that rewrites response bodies in front of the server
- Log/surface res.reason from the sign-in endpoint before treating the call as successful
When it happens
Trigger: Calling app.signIn with credentials when the server responds 200 without a token field — e.g. incompatible server version, an OpenID flow that did not finalize, or a proxy returning a non-standard body.
Common situations: Server/client version mismatch after an upgrade; reverse proxy stripping or altering the JSON body; pointing the client at a non-Actual endpoint that returns 200 OK with HTML/other JSON.
Related errors
- User ID is required for file creation
- token-not-found
- token-expired
- Authentication required. Set --password/--session-token, ACT
- Authentication required. Provide --password or --session-tok
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/aee69a5c431423ab.
Report an issue: GitHub.