{"record":{"id":"c9862577cf74b7f5","repo":"hashicorp/nomad","slug":"acl-bootstrap-already-done","errorCode":null,"errorMessage":"ACL bootstrap already done","messagePattern":"ACL bootstrap already done","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/state/state_store.go","lineNumber":6588,"sourceCode":"\t}\n\n\t// Return the reset index if we've already bootstrapped\n\treturn false, out.(*IndexEntry).Value, nil\n}\n\n// BootstrapACLTokens is used to create an initial ACL token.\nfunc (s *StateStore) BootstrapACLTokens(msgType structs.MessageType, index uint64, resetIndex uint64, token *structs.ACLToken) error {\n\ttxn := s.db.WriteTxnMsgT(msgType, index)\n\tdefer txn.Abort()\n\n\t// Check if we have already done a bootstrap\n\texisting, err := txn.First(\"index\", \"id\", \"acl_token_bootstrap\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"bootstrap check failed: %v\", err)\n\t}\n\tif existing != nil {\n\t\tif resetIndex == 0 {\n\t\t\treturn fmt.Errorf(\"ACL bootstrap already done\")\n\t\t} else if resetIndex != existing.(*IndexEntry).Value {\n\t\t\treturn fmt.Errorf(\"Invalid reset index for ACL bootstrap\")\n\t\t}\n\t}\n\n\t// Update the Create/Modify time\n\ttoken.CreateIndex = index\n\ttoken.ModifyIndex = index\n\n\t// Insert the token\n\tif err := txn.Insert(\"acl_token\", token); err != nil {\n\t\treturn fmt.Errorf(\"upserting token failed: %v\", err)\n\t}\n\n\t// Update the indexes table, prevents future bootstrap until reset\n\tif err := txn.Insert(\"index\", &IndexEntry{\"acl_token\", index}); err != nil {\n\t\treturn fmt.Errorf(\"index update failed: %v\", err)\n\t}","sourceCodeStart":6570,"sourceCodeEnd":6606,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/state/state_store.go#L6570-L6606","documentation":"Nomad allows ACL bootstrap exactly once; it records an 'acl_token_bootstrap' IndexEntry. This error is returned when a bootstrap is attempted again with resetIndex == 0 while that entry already exists. It is an intentional guard, not a bug.","triggerScenarios":"Running `nomad acl bootstrap` a second time without a reset index; calling UpsertACLTokens with an empty BootstrapSecretID reset.","commonSituations":"Operators forgetting the initial token was already created; scripted provisioning re-running bootstrap; cluster restored from backup already containing the bootstrap entry.","solutions":["Use the already-created initial management token instead of re-bootstrapping","Use `nomad acl bootstrap -reset=<index>` with the restore index if the token was lost","Recover the token via the documented recover flow (root-era: use a management token to create new ones)","If the token is irrecoverable, follow Nomad's ACL bootstrap reset procedure documented in the API"],"exampleFix":"// before\nnomad acl bootstrap  // second run, fails\n// after\nnomad acl bootstrap -reset=14  // reset index from backup metadata","handlingStrategy":"try-catch","validationCode":"list, err := client.ACL().TokensList(nil)\nif err == nil && len(list) > 0 { return fmt.Errorf(\"bootstrap already completed; use existing management token\") }","typeGuard":"func isAlreadyBootstrapped(err error) bool { return err != nil && err.Error() == \"ACL bootstrap already done\" }","tryCatchPattern":"token, _, err := client.ACL().Bootstrap(nil)\nif err != nil && err.Error() == \"ACL bootstrap already done\" {\n    return nil // expected on re-runs; fetch token from secret manager\n} else if err != nil {\n    return err\n}","preventionTips":["Store the initial management token in a secret manager immediately after first bootstrap","Make bootstrap scripts idempotent by treating this error as success","Never re-run bootstrap blindly in CI/CD provisioning"],"tags":["nomad","acl","bootstrap"],"backgroundTag":"acl-bootstrap-already-done","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"}