{"record":{"id":"90487472721e52b5","repo":"juanfont/headscale","slug":"user-s-not-found","errorCode":null,"errorMessage":"user %s not found","messagePattern":"user (.+?) not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"integration/helpers.go","lineNumber":1086,"sourceCode":"\t\tEmailVerified:     emailVerified,\n\t}\n}\n\n// GetUserByName retrieves a user by name from the headscale server.\n// This is a common pattern used when creating preauth keys or managing users.\nfunc GetUserByName(headscale ControlServer, username string) (*clientv1.User, error) {\n\tusers, err := headscale.ListUsers()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"listing users: %w\", err)\n\t}\n\n\tfor _, u := range users {\n\t\tif u.Name == username {\n\t\t\treturn u, nil\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"user %s not found\", username) //nolint:err113\n}\n\n// findNode returns the first node in nodes for which match returns true,\n// or nil if no node matches.\nfunc findNode(nodes []*clientv1.Node, match func(*clientv1.Node) bool) *clientv1.Node {\n\tfor _, n := range nodes {\n\t\tif match(n) {\n\t\t\treturn n\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// mustParseID parses a string ID emitted by the HTTP client types into a\n// uint64 for the APIs that still take numeric identifiers (NodeID, user and\n// key IDs). It panics on malformed input, which only happens if the server\n// emits a non-numeric ID — a bug worth failing the test loudly.","sourceCodeStart":1068,"sourceCodeEnd":1104,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/integration/helpers.go#L1068-L1104","documentation":"Returned by the integration helper GetUserByName when ListUsers succeeded but no user with the requested name exists. It is a plain fmt.Errorf (marked nolint:err113) with the missing username.","triggerScenarios":"Calling GetUserByName(t, headscale, \"nonexistent\") before that user was created, or after a cleanup removed it; also when the name has different case/whitespace than what was registered.","commonSituations":"Test ordering bugs — helper runs before the user-creation step; typos or case mismatches between the scenario users list and the lookup; parallel tests deleting shared users.","solutions":["Create the user (scenario.CreateUser / preauth flow) before calling GetUserByName","Verify the exact name string, including case, matches the one used at creation","Assert on list contents directly if absence is expected, instead of treating it as an error"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// create-if-missing pattern\nusers, _ := h.ListUsers()\nexists := slices.ContainsFunc(users, func(u *clientv1.User) bool { return u.Name == username })\nif !exists {\n    if _, err := scenario.CreateUser(username); err != nil {\n        return err\n    }\n}","typeGuard":null,"tryCatchPattern":"u, err := GetUserByName(h, username)\nif err != nil {\n    if strings.Contains(err.Error(), \"not found\") {\n        // create the user, then retry the lookup once\n    }\n    return err\n}","preventionTips":["Derive usernames from one constant per scenario to avoid typos/case drift","Order test steps: create users -> create nodes -> assert"],"tags":["integration-test","users","api"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}