{"record":{"id":"434f8dc75e080d37","repo":"hashicorp/nomad","slug":"cannot-specify-acl-role-id","errorCode":null,"errorMessage":"cannot specify ACL role ID","messagePattern":"cannot specify ACL role ID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"api/acl.go","lineNumber":279,"sourceCode":"// ACLRoles returns a new handle on the ACL roles API client.\nfunc (c *Client) ACLRoles() *ACLRoles {\n\treturn &ACLRoles{client: c}\n}\n\n// List is used to detail all the ACL roles currently stored within state.\nfunc (a *ACLRoles) List(q *QueryOptions) ([]*ACLRoleListStub, *QueryMeta, error) {\n\tvar resp []*ACLRoleListStub\n\tqm, err := a.client.query(\"/v1/acl/roles\", &resp, q)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn resp, qm, nil\n}\n\n// Create is used to create an ACL role.\nfunc (a *ACLRoles) Create(role *ACLRole, w *WriteOptions) (*ACLRole, *WriteMeta, error) {\n\tif role.ID != \"\" {\n\t\treturn nil, nil, errors.New(\"cannot specify ACL role ID\")\n\t}\n\tvar resp ACLRole\n\twm, err := a.client.put(\"/v1/acl/role\", role, &resp, w)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn &resp, wm, nil\n}\n\n// Update is used to update an existing ACL role.\nfunc (a *ACLRoles) Update(role *ACLRole, w *WriteOptions) (*ACLRole, *WriteMeta, error) {\n\tif role.ID == \"\" {\n\t\treturn nil, nil, errMissingACLRoleID\n\t}\n\tvar resp ACLRole\n\twm, err := a.client.put(\"/v1/acl/role/\"+role.ID, role, &resp, w)\n\tif err != nil {\n\t\treturn nil, nil, err","sourceCodeStart":261,"sourceCodeEnd":297,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/api/acl.go#L261-L297","documentation":"ACLRoles.Create() refuses to create a role that already carries an ID, since creation is meant to mint a new server-generated ID. Setting role.ID != \"\" usually means the caller intended an update, not a create, so the client fails fast to prevent accidentally creating a duplicate role.","triggerScenarios":"Calling ACLRoles.Create(role) where role.ID was populated — most often because the code path reuses a role struct fetched via Get/GetByName and passes it to Create instead of Update.","commonSituations":"Copy/paste of an update block into a create path; an 'upsert' helper that always calls Create first; deserializing a role from JSON that includes its ID and then calling Create.","solutions":["If the role already exists, call ACLRoles.Update(role) instead of Create.","If you truly want a new role, clear role.ID (role.ID = \"\") before Create so the server assigns a fresh ID.","Restructure upsert logic: try GetByName; if found, Update, else Create with a fresh struct without ID."],"exampleFix":"// before\nexisting, _, _ := client.ACL().Roles().GetByName(\"my-role\", nil)\nexisting.Description = \"new desc\"\n_, _, err := client.ACL().Roles().Create(existing, nil) // fails: cannot specify ACL role ID\n// after\nexisting.Description = \"new desc\"\n_, _, err := client.ACL().Roles().Update(existing, nil)","handlingStrategy":"validation","validationCode":"if role != nil && role.ID != \"\" {\n    return fmt.Errorf(\"use ACLRoles.Update for existing roles; Create refuses role.ID != \\\"\\\"\")\n}","typeGuard":"func isNewRole(r *api.ACLRole) bool { return r != nil && r.ID == \"\" }","tryCatchPattern":null,"preventionTips":["Implement upsert as: GetByName -> found ? Update : Create(fresh struct without ID).","Never reuse server-fetched role structs in Create calls.","When deserializing roles from JSON for creation, explicitly clear the ID field."],"tags":["consul","acl","create-vs-update","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}