{"record":{"id":"cacb6dc11852eb7b","repo":"semaphoreui/semaphore","slug":"your-account-already-has-a-linked-identity-for-thi","errorCode":null,"errorMessage":"Your account already has a linked identity for this provider. Unlink it first.","messagePattern":"Your account already has a linked identity for this provider\\. Unlink it first\\.","errorType":"http","errorClass":null,"httpStatus":409,"severity":"warning","filePath":"api/login.go","lineNumber":980,"sourceCode":"\t\tsessionUser, uErr := helpers.Store(r).GetUser(session.UserID)\n\t\tif uErr != nil {\n\t\t\tlog.Error(uErr.Error())\n\t\t\thttp.Error(w, \"Failed to link external account.\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tif lErr := linkExternalIdentity(helpers.Store(r), sessionUser, db.IdentityTypeOidc, pid, claims.sub); lErr != nil {\n\t\t\tlog.WithError(lErr).WithFields(log.Fields{\n\t\t\t\t\"user_id\":  sessionUser.ID,\n\t\t\t\t\"provider\": pid,\n\t\t\t\t\"context\":  \"oidc_link\",\n\t\t\t}).Error(\"Failed to link external identity\")\n\n\t\t\tswitch {\n\t\t\tcase errors.Is(lErr, errIdentityLinkedToAnother):\n\t\t\t\thttp.Error(w, \"This external account is already linked to another user.\", http.StatusConflict)\n\t\t\tcase errors.Is(lErr, errProviderAlreadyLinked):\n\t\t\t\thttp.Error(w, \"Your account already has a linked identity for this provider. Unlink it first.\", http.StatusConflict)\n\t\t\tdefault:\n\t\t\t\thttp.Error(w, \"Failed to link external account.\", http.StatusInternalServerError)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tredirectURL, _ := url.JoinPath(util.Config.WebHost, \"/\")\n\t\thttp.Redirect(w, r, redirectURL, http.StatusTemporaryRedirect)\n\t\treturn\n\t}\n\n\tuser, err := resolveExternalUser(helpers.Store(r), externalUserProfile{\n\t\tType:          db.IdentityTypeOidc,\n\t\tProvider:      pid,\n\t\tExternalUID:   claims.sub,\n\t\tUsername:      claims.username,\n\t\tName:          claims.name,\n\t\tEmail:         claims.email,","sourceCodeStart":962,"sourceCodeEnd":998,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/api/login.go#L962-L998","documentation":"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.","triggerScenarios":"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.","commonSituations":"Double-submit of the link form; user forgot the provider was already linked; wanting to switch IdP accounts requires unlinking first by design.","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"],"exampleFix":"// before: linking again without unlinking\nPOST /auth/link/oidc/keycloak  // 409\n// after\nDELETE /auth/unlink/oidc/keycloak\nPOST /auth/link/oidc/keycloak","handlingStrategy":"validation","validationCode":"ids, _ := store.ListIdentities(sessionUser.ID)\nfor _, id := range ids {\n    if id.Type == db.IdentityTypeOidc && id.Provider == pid {\n        return errors.New(\"provider already linked for this user\")\n    }\n}","typeGuard":null,"tryCatchPattern":"switch {\ncase errors.Is(lErr, errProviderAlreadyLinked):\n    http.Error(w, \"Your account already has a linked identity for this provider. Unlink it first.\", http.StatusConflict)\n    return\n}","preventionTips":["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"],"tags":["oidc","conflict","http-409"],"backgroundTag":"resource-already-exists","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}