passbolt/passbolt_api · critical · InternalErrorException
The SSO state could not be saved.
Error message
The SSO state could not be saved.
What it means
After assertions, consume() marks the SSO state as used by setting deleted = now() and saving it. If the save fails, an InternalErrorException (HTTP 500) is thrown. Note the state is only 'consumed' via the deleted timestamp (soft delete); the save is essential to make single-use states unusable, so a failure here is treated as a server fault.
Solutions
- Check database health and application error logs for the underlying save failure (connection refused, deadlock, disk full).
- Run pending migrations (passbolt migrate / ddev refresh) to ensure the sso_states schema exists and matches the code version.
- Retry the SSO flow once the database is available — the state may remain unconsumed; if in doubt, re-initiate.
- If it recurs on every callback, inspect SsoStatesTable validation/rules for a rule rejecting the save and check the entity's fields against the schema.
Example fix
# before: schema drift after upgrade # after ddev refresh # or: bin/cake passbolt migrate # verify sso_states table exists with 'deleted' column
Defensive patterns
Strategy: retry
Validate before calling
// Pre-check DB availability before SSO callback processing
try {
$conn = ConnectionManager::get('default');
$conn->execute('SELECT 1 FROM sso_states LIMIT 1');
} catch (Throwable $e) {
// DB unreachable: fail fast instead of a 500 mid-consume
} Type guard
function isSavable(SsoState $ssoState): bool {
return $ssoState->id !== null && $ssoState->deleted === null;
} Try / catch
try {
$service->assertAndConsume($ssoState, $settingsId, $uac);
} catch (InternalErrorException $e) {
if ($e->getMessage() === 'The SSO state could not be saved.') {
// check DB health / run migrations, then retry the flow
}
throw $e;
} Prevention
- Run bin/cake passbolt migrate after every upgrade.
- Monitor DB connectivity and disk space.
- Check error logs for the real save failure (validation rule, deadlock, connection).
- Verify sso_states schema has the deleted column.
When it happens
Trigger: SsoStatesTable->save($ssoState) returns false during consume(), called at the end of assertAndConsume/assertAndConsumeWithoutUser (both on success and when re-throwing an assertion failure). Typical causes: entity validation errors on save, database connection failure, missing sso_states table, or the entity marked dirty-but-invalid.
Common situations: Database outage or connection limit reached during an SSO callback; migration not run so the sso_states table or columns (deleted) are missing; write permissions/replication issues on the DB; lock/table corruption in MySQL/Postgres.
Related errors
- Could not delete the draft SSO settings.
- Could not delete the SSO settings.
- Could not save the SSO state, please try again later.
- Could not save the user data. Please try again later.
- Could not update the SSO settings.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/c269933b8845fe9b.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltEe/Sso/src/Service/SsoStates/SsoStatesAssertService.php:169
}
}
/**
* Marks given state as deleted.
*
* @param \Passbolt\Sso\Model\Entity\SsoState $ssoState SSO state entity.
* @return void
* @throws \Cake\Http\Exception\InternalErrorException When unable to save the SSO state entity.
*/
private function consume(SsoState $ssoState): void
{
/** @var \Passbolt\Sso\Model\Table\SsoStatesTable $ssoStatesTable */
$ssoStatesTable = $this->fetchTable('Passbolt/Sso.SsoStates');
$ssoState->deleted = DateTime::now();
if (!$ssoStatesTable->save($ssoState)) {
throw new InternalErrorException(__('The SSO state could not be saved.'));
}
}
}
View on GitHub (pinned to 31c1bbc10f)