{"record":{"id":"b6c31f51fa4d6480","repo":"gastownhall/beads","slug":"invalid-metadata-key-filter-w","errorCode":null,"errorMessage":"invalid metadata key filter: %w","messagePattern":"invalid metadata key filter: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/workapi/metadata.go","lineNumber":28,"sourceCode":"// ValidateMetadataFilters refuses a metadata key the query layer cannot spell.\n//\n// Both builders call it, so the leaf contract's \"keys are validated inside\" is\n// true of every Reader implementation rather than of the CLI only. Before it\n// existed the sole check lived in the SQL builder, whose error surfaced wrapped\n// in the storage method's name — a shape nothing above storage can classify —\n// so a typo'd key reached `bd list` as a usage error and the HTTP surface as a\n// 500, on a parameter the frozen document promises a 400 for.\n//\n// The CLI still validates the same input at flag-parse time and still reports\n// it in its own words: this is the floor under every caller, not a replacement\n// for a front door's usage error.\n//\n// Keys are checked in sorted order so a request with two bad keys always names\n// the same one.\nfunc ValidateMetadataFilters(fields map[string]string, hasKey string) error {\n\tif hasKey != \"\" {\n\t\tif err := storage.ValidateMetadataKey(hasKey); err != nil {\n\t\t\treturn fmt.Errorf(\"invalid metadata key filter: %w\", err)\n\t\t}\n\t}\n\tkeys := make([]string, 0, len(fields))\n\tfor k := range fields {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\tfor _, k := range keys {\n\t\tif err := storage.ValidateMetadataKey(k); err != nil {\n\t\t\treturn fmt.Errorf(\"invalid metadata field key: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n","sourceCodeStart":10,"sourceCodeEnd":43,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/workapi/metadata.go#L10-L43","documentation":"ValidateMetadataFilters rejects the --has-key (hasKey) argument when storage.ValidateMetadataKey deems it malformed. The library validates metadata keys up front so a bad key fails fast instead of producing a filter that can never match. The wrapped inner error from ValidateMetadataKey names the specific rule violated.","triggerScenarios":"Calling BuildListFilter or BuildReadyFilter with fields/hasKey where hasKey fails ValidateMetadataKey — e.g. an empty-or-whitespace key, a key with illegal characters, or one exceeding the length cap.","commonSituations":"Scripting `bd list --has-key` with a value containing spaces or dots; passing a JSON-style dotted path when only flat alphanumeric keys are allowed; copy-pasting a label that includes trailing whitespace.","solutions":["Read the wrapped ValidateMetadataKey error to see which rule the key breaks","Normalize the key: trim whitespace, remove disallowed characters, match the allowed pattern/length","If the intent is to filter on a field, verify the exact key with `bd show` on a known issue carrying that metadata"],"exampleFix":"// before\nbd list --has-key \"review status\"\n// after\nbd list --has-key \"review-status\"","handlingStrategy":"validation","validationCode":"if hasKey != \"\" {\n\tif err := storage.ValidateMetadataKey(hasKey); err != nil {\n\t\treturn fmt.Errorf(\"--has-key: %w\", err)\n\t}\n}","typeGuard":null,"tryCatchPattern":"err := ValidateMetadataFilters(fields, hasKey)\nif err != nil && strings.HasPrefix(err.Error(), \"invalid metadata key filter\") {\n\treturn fmt.Errorf(\"check --has-key value: %w\", err)\n}","preventionTips":["Trim and sanitize key input before passing --has-key","Reuse storage.ValidateMetadataKey in your own flag parsing for early feedback","Keep metadata keys to the documented charset and length"],"tags":["cli","validation","metadata","filtering"],"backgroundTag":"invalid-metadata-key","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}