{"record":{"id":"ddd3c03e27723b95","repo":"hashicorp/nomad","slug":"failed-to-decode-key-q-to-string","errorCode":null,"errorMessage":"failed to decode key %q to string","messagePattern":"failed to decode key %q to string","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"command/agent/config_parse.go","lineNumber":639,"sourceCode":"\t}\n\n\tfor idx, obj := range keyringBlocks.Items {\n\t\tprovider := c.KEKProviders[idx]\n\t\tif len(provider.ExtraKeysHCL) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tprovider.Config = map[string]string{}\n\n\t\tvar m map[string]any\n\t\tif err := hcl.DecodeObject(&m, obj.Val); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, extraKey := range provider.ExtraKeysHCL {\n\t\t\tval, ok := m[extraKey].(string)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"failed to decode key %q to string\", extraKey)\n\t\t\t}\n\t\t\tprovider.Config[extraKey] = val\n\t\t}\n\n\t\t// clear the extra keys for these blocks because we've already handled\n\t\t// them and don't want them to bubble up to the caller\n\t\tprovider.ExtraKeysHCL = nil\n\t}\n\n\tsort.Slice(c.KEKProviders, func(i, j int) bool {\n\t\treturn c.KEKProviders[i].ID() < c.KEKProviders[j].ID()\n\t})\n\n\treturn nil\n}\n","sourceCodeStart":621,"sourceCodeEnd":655,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/command/agent/config_parse.go#L621-L655","documentation":"parseKeyringConfigs decodes keyring provider blocks into a map, then for each key listed in the provider's ExtraKeysHCL asserts the decoded value is a string before copying it into provider.Config. This error names the key whose decoded value was not a string (e.g. a number, bool, or list).","triggerScenarios":"A keyring block containing an extra key (one handled specially by the provider, listed in ExtraKeysHCL) with a non-string HCL value, e.g. `region = [\"us-east-1\"]` or `kms_key_id = 12345`, reached via ParseConfigFile/LoadConfig.","commonSituations":"Unquoted numbers/booleans in keyring provider config (AWS region lists, key IDs); schema drift between vault versions where a key moved into ExtraKeysHCL and now must be a string; templating that interpolates non-string types.","solutions":["Quote the offending key's value so HCL decodes it as a string: `kms_key_id = \"12345\"`","Check the key name in the error message (%q) and locate it in the keyring block","Flatten lists to a comma-separated string if the provider expects one value","Confirm against the provider docs which keys are extra keys requiring string values"],"exampleFix":"// before\nkeyring {\n  provider = \"awskms\"\n  kms_key_id = 12345\n}\n\n// after\nkeyring {\n  provider = \"awskms\"\n  kms_key_id = \"12345\"\n}","handlingStrategy":"validation","validationCode":"func assertKeyringExtraKeysAreStrings(keyring map[string]any, extraKeys []string) error {\n\tfor _, k := range extraKeys {\n\t\tif v, present := keyring[k]; present {\n\t\t\tif _, ok := v.(string); !ok {\n\t\t\t\treturn fmt.Errorf(\"keyring key %q must be a string, got %T\", k, v)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}","typeGuard":"func isString(v any) bool {\n\t_, ok := v.(string)\n\treturn ok\n}","tryCatchPattern":"cfg, err := ParseConfigFile(path)\nif err != nil {\n\tif strings.Contains(err.Error(), \"failed to decode key\") {\n\t\treturn fmt.Errorf(\"keyring extra key must be a quoted string in %s: %w\", path, err)\n\t}\n\treturn err\n}","preventionTips":["Quote all extra-key values in keyring blocks (numbers, bools, lists all fail)","Keep numbers like key IDs as quoted strings","After upgrading versions, re-check which keys are ExtraKeysHCL strings","Type-check decoded keyring maps in a pre-parse validation step"],"tags":["hcl","keyring","type-error","config-parsing"],"backgroundTag":"config-value-type-mismatch","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"}