actualbudget/actual · error
error
Error message
error
What it means
When loginWithOpenIdSetup() returns an error, the /login endpoint responds 400 with { status: 'error', reason: error }. This covers OpenID Connect setup failures — provider discovery failing, missing/misconfigured issuer or client credentials, or the setup module rejecting request parameters. The raw setup error string is passed through as the reason.
Source
Thrown at packages/sync-server/src/app-account.js:110
return;
}
}
break;
}
case 'openid': {
if (!isValidRedirectUrl(req.body.returnUrl)) {
res
.status(400)
.send({ status: 'error', reason: 'Invalid redirect URL' });
return;
}
const { error, url } = await loginWithOpenIdSetup(
req.body.returnUrl,
req.body.password,
);
if (error) {
res.status(400).send({ status: 'error', reason: error });
return;
}
res.send({ status: 'ok', data: { returnUrl: url } });
return;
}
default:
tokenRes = await loginWithPassword(req.body.password);
break;
}
const { error, token } = tokenRes;
if (error) {
res.status(400).send({ status: 'error', reason: error });
return;
}
res.send({ status: 'ok', data: { token } });View on GitHub (pinned to d4334cb6e6)
Solutions
- Read the 'reason' field of the 400 response — it carries the concrete OpenID error
- Verify OPENID_ISSUER, OPENID_CLIENT_ID, OPENID_CLIENT_SECRET (and discovery URL) are correct and reachable from the server
- Test the discovery document manually (curl <issuer>/.well-known/openid-configuration) from the server host
- Match OPENID_AUTH_METHOD to what the IdP supports and supply the password if required
- After fixing config, restart the sync-server so the OpenID client re-initializes
Example fix
// before
const { error, url } = await loginWithOpenIdSetup(req.body.returnUrl, req.body.password);
// error: 'failed to discover issuer'
// after
// OPENID_ISSUER=https://idp.example.com/realms/main (corrected, reachable)
const { error, url } = await loginWithOpenIdSetup(req.body.returnUrl, req.body.password);
if (!error) res.send({ status: 'ok', data: { returnUrl: url } }); Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: IdP discovery reachable from server
const cfg = await fetch(`${OPENID_ISSUER}/.well-known/openid-configuration`);
if (!cfg.ok) throw new Error('OpenID discovery unreachable'); Type guard
function isOpenIdSetupError(res, body) {
return res.status === 400 && body?.status === 'error' && typeof body?.reason === 'string';
} Try / catch
const res = await request('/login', { method: 'POST', body: { loginMethod: 'openid', returnUrl, password } });
if (res.status === 400) {
const body = await res.json();
if (body?.status === 'error') {
showLoginError(body.reason); // e.g. discovery failed / bad client credentials
return;
}
} Prevention
- Validate OPENID_* env vars at server startup with a fail-fast check
- Monitor IdP discovery endpoint reachability from the server host
- Match OPENID_AUTH_METHOD to IdP capabilities; supply password when required
- Restart the server after any OpenID configuration change
When it happens
Trigger: POST to /login with loginMethod 'openid' and a valid returnUrl, but loginWithOpenIdSetup fails: unreachable discovery document, invalid client_id/client_secret, unsupported auth method, or bad openid configuration values.
Common situations: Wrong OPENID_ISSUER or OPENID_CLIENT_ID/SECRET env vars; IdP discovery endpoint unreachable from the server (firewall, DNS); IdP metadata lacks required endpoints; auth method unsupported by the provider; password omitted or wrong when OPENID_AUTH_METHOD requires it.
Related errors
- openid-grant-failed
- Invalid user IDs
- New owner not found
- Failed to transfer files: ${error.message}
- boot?.error
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/686c9934f2905269.
Report an issue: GitHub.