semaphoreui/semaphore · error
Failed to link external account.
Error message
Failed to link external account.
What it means
During the link flow, oidcRedirect loads the logged-in user via helpers.Store(r).GetUser(session.UserID). If the store returns an error (user row missing, DB failure), it returns HTTP 500 'Failed to link external account.' The detailed cause goes to the log only.
Solutions
- Check the server log for the underlying GetUser error
- Verify the user in session still exists in the database (users table)
- Clear the stale session (log out/in) so a deleted-user session is not reused
- Check database connectivity and recent migrations for integrity issues
Example fix
-- before: session points at deleted user DELETE FROM sessions WHERE user_id NOT IN (SELECT id FROM users); -- after: sessions cleaned, user re-logs in and retries linking
Defensive patterns
Strategy: validation
Validate before calling
if _, err := helpers.Store(r).GetUser(session.UserID); err != nil {
// invalidate stale session before proceeding
return errors.New("session user no longer exists")
} Try / catch
sessionUser, uErr := helpers.Store(r).GetUser(session.UserID)
if uErr != nil {
log.Errorf("GetUser(%s) failed: %v", session.UserID, uErr)
// clear session and force re-login
clearSession(w, r)
return
} Prevention
- Invalidate sessions when deleting users
- Monitor DB health for the store backend
- Add a foreign-key/consistency check between sessions and users
When it happens
Trigger: Session references a user that was deleted from the database while the session was still valid; database connectivity/query error in GetUser; replica/read inconsistency where the user record isn't visible.
Common situations: Admin deleted a user account that still had an active session; transient DB outage during linking; data migration left orphaned sessions pointing at removed user IDs.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- Internal Server Error
- You must be signed in to link an external account.
- OIDC sign-in failed: could not find or create the user…
- OIDC sign-in failed: invalid redirect URL.
- Error generating key
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/5c9775d784707175.
Report an issue: GitHub.
Appendix: source
Thrown at api/login.go:965
}
if claims.sub == "" {
log.Error(fmt.Errorf("oidc provider %s returned no sub claim", pid))
http.Error(w, "OIDC sign-in failed: the provider returned no user ID (sub claim). Contact your administrator.", http.StatusBadGateway)
return
}
if stateData.Link {
session, ok := getSession(r)
if !ok || !session.IsVerified() {
http.Error(w, "You must be signed in to link an external account.", http.StatusUnauthorized)
return
}
sessionUser, uErr := helpers.Store(r).GetUser(session.UserID)
if uErr != nil {
log.Error(uErr.Error())
http.Error(w, "Failed to link external account.", http.StatusInternalServerError)
return
}
if lErr := linkExternalIdentity(helpers.Store(r), sessionUser, db.IdentityTypeOidc, pid, claims.sub); lErr != nil {
log.WithError(lErr).WithFields(log.Fields{
"user_id": sessionUser.ID,
"provider": pid,
"context": "oidc_link",
}).Error("Failed to link external identity")
switch {
case errors.Is(lErr, errIdentityLinkedToAnother):
http.Error(w, "This external account is already linked to another user.", http.StatusConflict)
case errors.Is(lErr, errProviderAlreadyLinked):
http.Error(w, "Your account already has a linked identity for this provider. Unlink it first.", http.StatusConflict)
default:
http.Error(w, "Failed to link external account.", http.StatusInternalServerError)
}View on GitHub (pinned to 1774ccb71a)