semaphoreui/semaphore · warning
This external account is already linked to another user.
Error message
This external account is already linked to another user.
What it means
linkExternalIdentity rejected the link because the external identity (provider pid + claims.sub) is already attached to a different Semaphore user (errIdentityLinkedToAnother). The handler maps this sentinel to HTTP 409 with this message. This prevents one external account from being linked to multiple local users.
Solutions
- Unlink the external account from the other Semaphore user first (or have an admin remove that identity row), then retry linking
- Sign in with the original account that already owns this external identity instead of linking it again
- Check the identities table for the (provider, external_uid) pair to see which user owns it
- If the other account is stale, delete it or its identity so the link can proceed
Example fix
-- find conflicting identity SELECT u.username FROM identities i JOIN users u ON u.id = i.user_id WHERE i.provider='keycloak' AND i.external_uid='<sub>'; -- after: unlink/delete from the other user, then retry link
Defensive patterns
Strategy: validation
Validate before calling
existing, err := store.GetIdentityByExternalUID(db.IdentityTypeOidc, pid, claims.sub)
if err == nil && existing.UserID != sessionUser.ID {
return fmt.Errorf("identity already linked to user %s", existing.UserID)
} Try / catch
if lErr := linkExternalIdentity(store, user, db.IdentityTypeOidc, pid, sub); lErr != nil {
if errors.Is(lErr, errIdentityLinkedToAnother) {
http.Error(w, "This external account is already linked to another user.", http.StatusConflict)
return
}
} Prevention
- Show currently linked identities in the UI before offering link
- Do not share one IdP account across multiple Semaphore users
- Provide admin tooling to inspect/unlink identities
When it happens
Trigger: Link flow where the same IdP account (same sub) was previously linked to another Semaphore user; two local accounts (e.g. created via different providers or manually) both attempting to claim the same external identity; shared/testing IdP account used by multiple users.
Common situations: User created a second Semaphore account and tries to link their existing corporate SSO identity already used by the first account; shared service account at the IdP; leftover identity row from a previous user with the same sub.
Related errors
- Your account already has a linked identity for this…
- 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/2ff13842aadac086.
Report an issue: GitHub.
Appendix: source
Thrown at api/login.go:978
}
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,View on GitHub (pinned to 1774ccb71a)