{"record":{"id":"4f05f5ddcb54896c","repo":"juanfont/headscale","slug":"w-token-q-found-s","errorCode":null,"errorMessage":"%w: token %q found: %s","messagePattern":"%w: token %q found: (.+?)","errorType":"validation","errorClass":"ErrMultipleUsersFound","httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":413,"sourceCode":"\tuTrimmed := strings.TrimSuffix(u.String(), \"@\")\n\n\tfor _, user := range users {\n\t\tif user.ProviderIdentifier.Valid && user.ProviderIdentifier.String == uTrimmed {\n\t\t\t// Prioritize ProviderIdentifier match and exit early\n\t\t\treturn user, nil\n\t\t}\n\n\t\tif user.Email == uTrimmed || user.Name == uTrimmed {\n\t\t\tpotentialUsers = append(potentialUsers, user)\n\t\t}\n\t}\n\n\tif len(potentialUsers) == 0 {\n\t\treturn types.User{}, fmt.Errorf(\"%w: token %q\", ErrUserNotFound, u.String())\n\t}\n\n\tif len(potentialUsers) > 1 {\n\t\treturn types.User{}, fmt.Errorf(\"%w: token %q found: %s\", ErrMultipleUsersFound, u.String(), potentialUsers.String())\n\t}\n\n\treturn potentialUsers[0], nil\n}\n\nfunc (u *Username) Resolve(_ *Policy, users types.Users, nodes views.Slice[types.NodeView]) (ResolvedAddresses, error) {\n\treturn newResolvedAddresses(u.resolve(nil, users, nodes))\n}\n\nfunc (u *Username) resolve(_ *Policy, users types.Users, nodes views.Slice[types.NodeView]) (*netipx.IPSet, error) {\n\tvar (\n\t\tips  netipx.IPSetBuilder\n\t\terrs []error\n\t)\n\n\tuser, err := u.resolveUser(users)\n\tif err != nil {\n\t\terrs = append(errs, err)","sourceCodeStart":395,"sourceCodeEnd":431,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L395-L431","documentation":"Username.resolve matched more than one user for the same token — the username or email is ambiguous across the database. headscale refuses to guess, so resolution fails with ErrMultipleUsersFound and lists the matching users.","triggerScenarios":"Two users share the same Name (e.g. both named 'alice' under different providers) or the same Email, and the policy references that token. len(potentialUsers) > 1 after the scan in Username.resolve.","commonSituations":"OIDC migration leaving duplicate rows (one created by CLI, one by IdP) with identical emails; multiple providers feeding one headscale; test fixtures that seed users with colliding names.","solutions":["Inspect the 'found:' list in the error and delete or rename the duplicate users ('headscale users rename' / 'headscale users destroy').","Reference one of the users by its unique ProviderIdentifier instead of the shared email.","Fix the IdP or registration flow that created duplicates, then deduplicate.","In tests, ensure each seeded user has a distinct email and name."],"exampleFix":"# before: two users both named alice\nheadscale users list   # alice (id 1), alice (id 2)\n\n# after: keep one, rename the other\nheadscale users rename -i 2 alice-2","handlingStrategy":"try-catch","validationCode":"// Detect ambiguous tokens before compile.\nmatches := 0\nfor _, u := range users {\n    if u.Email == token || u.Name == token { matches++ }\n}\nif matches > 1 { return fmt.Errorf(\"token %q matches %d users\", token, matches) }","typeGuard":null,"tryCatchPattern":"user, err := username.Resolve(pol, users, nodes)\nif err != nil {\n    if errors.Is(err, v2.ErrMultipleUsersFound) {\n        // error message lists the duplicates; deduplicate via users rename/destroy\n    }\n    return err\n}","preventionTips":["Enforce unique emails and usernames when provisioning users.","After IdP migrations, audit for duplicate rows before loading policies.","Reference ProviderIdentifier (unique) rather than shared emails where possible."],"tags":["policy","acl","user-lookup","data-integrity","go"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}