{"record":{"id":"6a3d9233c35b181b","repo":"hashicorp/nomad","slug":"root-key-id-is-required","errorCode":null,"errorMessage":"root key ID is required","messagePattern":"root key ID is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/keyring_endpoint.go","lineNumber":277,"sourceCode":"}\n\n// Get retrieves an existing key from the keyring, including both the\n// key material and metadata. It is used only for replication.\nfunc (k *Keyring) Get(args *structs.KeyringGetRootKeyRequest, reply *structs.KeyringGetRootKeyResponse) error {\n\taclObj, err := k.srv.AuthenticateServerOnly(k.ctx, args)\n\tk.srv.MeasureRPCRate(\"keyring\", structs.RateMetricRead, args)\n\n\tif err != nil || !aclObj.AllowServerOp() {\n\t\treturn structs.ErrPermissionDenied\n\t}\n\n\tif done, err := k.srv.forward(\"Keyring.Get\", args, args, reply); done {\n\t\treturn err\n\t}\n\tdefer metrics.MeasureSince([]string{\"nomad\", \"keyring\", \"get\"}, time.Now())\n\n\tif args.KeyID == \"\" {\n\t\treturn fmt.Errorf(\"root key ID is required\")\n\t}\n\n\t// Setup the blocking query\n\topts := blockingOptions{\n\t\tqueryOpts: &args.QueryOptions,\n\t\tqueryMeta: &reply.QueryMeta,\n\t\trun: func(ws memdb.WatchSet, s *state.StateStore) error {\n\n\t\t\tsnap, err := k.srv.fsm.State().Snapshot()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\twrappedKey, err := snap.RootKeyByID(ws, args.KeyID)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif wrappedKey == nil {\n\t\t\t\treturn k.srv.replySetIndex(state.TableRootKeys, &reply.QueryMeta)","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/keyring_endpoint.go#L259-L295","documentation":"The keyring Get endpoint requires a KeyID to look up. Since Get is a point-read of one root key, an empty KeyID cannot identify any key and is rejected up front after forwarding, before the blocking query is set up.","triggerScenarios":"Calling the Keyring.Get RPC (or GET /v1/kms/keys/<id>) with args.KeyID empty string — e.g. omitting the key ID in the URL path or passing an unset variable.","commonSituations":"A shell variable holding the key ID is empty/unset in a script; a UI or automation passes a nil-tenant placeholder; a wrapper function's argument is dropped.","solutions":["Populate args.KeyID with the root key ID before calling Get.","List keys first (keyring list endpoint) to obtain a valid KeyID.","Add a client-side check that KeyID is non-empty before issuing the RPC."],"exampleFix":"// before\nreply, err := client.Keyring().Get(&structs.KeyringGetRootKeyRequest{})\n// after\nif keyID == \"\" { return fmt.Errorf(\"keyID must be set\") }\nreply, err := client.Keyring().Get(&structs.KeyringGetRootKeyRequest{KeyID: keyID})","handlingStrategy":"validation","validationCode":"if keyID == \"\" {\n    return fmt.Errorf(\"cannot Get keyring key: keyID is empty\")\n}","typeGuard":"func keyIDProvided(req *structs.KeyringGetRootKeyRequest) bool {\n    return req != nil && req.KeyID != \"\"\n}","tryCatchPattern":"reply, err := client.Keyring().Get(req, nil)\nif err != nil && strings.Contains(err.Error(), \"root key ID is required\") {\n    return fmt.Errorf(\"caller bug: KeyID was empty; check %q source variable\", idVar)\n}","preventionTips":["Check shell/CI variables for the key ID before invoking the API.","List keys first to obtain valid IDs programmatically.","Trim and non-empty-check IDs parsed from URLs or user input."],"tags":["nomad","keyring","validation","missing-parameter"],"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"}