{"record":{"id":"207d29affe8aff0b","repo":"gastownhall/beads","slug":"has-metadata-key-only-supports-operator","errorCode":null,"errorMessage":"has_metadata_key only supports = operator","messagePattern":"has_metadata_key only supports = operator","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/query/evaluator.go","lineNumber":518,"sourceCode":"func (e *Evaluator) applyMetadataFilter(comp *ComparisonNode, filter *types.IssueFilter) error {\n\tif comp.Op != OpEquals {\n\t\treturn fmt.Errorf(\"metadata fields only support = operator\")\n\t}\n\tkey := strings.TrimPrefix(comp.Field, \"metadata.\")\n\tif err := storage.ValidateMetadataKey(key); err != nil {\n\t\treturn err\n\t}\n\tif filter.MetadataFields == nil {\n\t\tfilter.MetadataFields = make(map[string]string)\n\t}\n\tfilter.MetadataFields[key] = comp.Value\n\treturn nil\n}\n\n// applyHasMetadataKeyFilter handles has_metadata_key=<keyname> queries (GH#1406).\nfunc (e *Evaluator) applyHasMetadataKeyFilter(comp *ComparisonNode, filter *types.IssueFilter) error {\n\tif comp.Op != OpEquals {\n\t\treturn fmt.Errorf(\"has_metadata_key only supports = operator\")\n\t}\n\tif err := storage.ValidateMetadataKey(comp.Value); err != nil {\n\t\treturn err\n\t}\n\tfilter.HasMetadataKey = comp.Value\n\treturn nil\n}\n\n// buildMetadataPredicate builds a predicate for metadata.<key>=<value> in OR queries.\n// Parses the issue's JSON metadata and compares the top-level scalar at the given key.\nfunc (e *Evaluator) buildMetadataPredicate(comp *ComparisonNode) (func(*types.Issue) bool, error) {\n\tif comp.Op != OpEquals {\n\t\treturn nil, fmt.Errorf(\"metadata fields only support = operator\")\n\t}\n\tkey := strings.TrimPrefix(comp.Field, \"metadata.\")\n\tif err := storage.ValidateMetadataKey(key); err != nil {\n\t\treturn nil, err\n\t}","sourceCodeStart":500,"sourceCodeEnd":536,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/query/evaluator.go#L500-L536","documentation":"has_metadata_key tests for the existence of a metadata key and is implemented only as exact equality with a key name. applyHasMetadataKeyFilter runs in filter mode; any operator other than = is rejected. Existence testing has no meaningful != / =~ semantics in the storage filter, so the engine fails fast.","triggerScenarios":"A query like `has_metadata_key!=team` or `has_metadata_key=~te` dispatched from applyComparison to applyHasMetadataKeyFilter with comp.Op != OpEquals.","commonSituations":"Trying to express 'issues missing key X' with !=; attempting wildcard/prefix key matching; copy-pasting operator styles from other query languages (SQL IS NULL / IS NOT NULL analogies).","solutions":["Use `has_metadata_key=team` — the only supported form","To find issues missing a key, list issues with has_metadata_key=team and exclude them client-side (there is no NOT support for has_metadata_key in filter mode)","In OR/predicate mode the equivalent restriction applies (see buildHasMetadataKeyPredicate); keep the same = form there","Match the key name exactly as stored; keys are validated by storage.ValidateMetadataKey"],"exampleFix":"// before\nbd list 'has_metadata_key!=team'\n// after\nbd list 'has_metadata_key=team'","handlingStrategy":"validation","validationCode":"if field == \"has_metadata_key\" && op != \"=\" {\n    return fmt.Errorf(\"has_metadata_key requires = (got %s)\", op)\n}","typeGuard":"func isHasMetadataKeyEquals(field, op string) bool {\n    return field == \"has_metadata_key\" && op == \"=\"\n}","tryCatchPattern":null,"preventionTips":["Always write has_metadata_key=<keyname>","Do not attempt != or prefix matching on key names","For 'key absent' checks, exclude matched results client-side","Use exact stored key names"],"tags":["query","metadata","operator-not-supported"],"backgroundTag":"query-operator-not-supported","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}