semaphoreui/semaphore · warning
Your account already has a linked identity for this…
Error message
Your account already has a linked identity for this provider. Unlink it first.
What it means
linkExternalIdentity returned errProviderAlreadyLinked: the current session's user already has a linked identity of type OIDC for this provider (pid), so linking another one (or the same one again) is refused. Mapped to HTTP 409.
Solutions
- Unlink the existing OIDC identity for that provider in user settings, then link the new one
- Refresh the account settings page to see the link already exists - no action needed on a duplicate retry
- If a stale/broken identity row must be replaced, have an admin remove it from the identities table first
Example fix
// before: linking again without unlinking POST /auth/link/oidc/keycloak // 409 // after DELETE /auth/unlink/oidc/keycloak POST /auth/link/oidc/keycloak
Defensive patterns
Strategy: validation
Validate before calling
ids, _ := store.ListIdentities(sessionUser.ID)
for _, id := range ids {
if id.Type == db.IdentityTypeOidc && id.Provider == pid {
return errors.New("provider already linked for this user")
}
} Try / catch
switch {
case errors.Is(lErr, errProviderAlreadyLinked):
http.Error(w, "Your account already has a linked identity for this provider. Unlink it first.", http.StatusConflict)
return
} Prevention
- Disable/gray out the Link button for already-linked providers
- Make unlink-then-link a single guided flow for switching accounts
- Guard against double submission on the link form
When it happens
Trigger: User clicks 'Link account' for a provider they already linked; user tries to link a second account from the same IdP while one is attached; retry of a link operation that actually succeeded but the UI didn't reflect it.
Common situations: Double-submit of the link form; user forgot the provider was already linked; wanting to switch IdP accounts requires unlinking first by design.
Related errors
- This external account is already linked to another user.
- Account linking must be initiated with a POST request.
- You must be signed in to link an external account.
- OIDC sign-in failed: state cookie is missing. Try signing…
- OIDC sign-in failed: invalid state. Try signing in again.
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/cacb6dc11852eb7b.
Report an issue: GitHub.
Appendix: source
Thrown at api/login.go:980
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)
}
return
}
redirectURL, _ := url.JoinPath(util.Config.WebHost, "/")
http.Redirect(w, r, redirectURL, http.StatusTemporaryRedirect)
return
}
user, err := resolveExternalUser(helpers.Store(r), externalUserProfile{
Type: db.IdentityTypeOidc,
Provider: pid,
ExternalUID: claims.sub,
Username: claims.username,
Name: claims.name,
Email: claims.email,View on GitHub (pinned to 1774ccb71a)