gastownhall/beads · error

has_metadata_key only supports = operator

Error message

has_metadata_key only supports = operator

What it means

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.

Source

Thrown at internal/query/evaluator.go:518

func (e *Evaluator) applyMetadataFilter(comp *ComparisonNode, filter *types.IssueFilter) error {
	if comp.Op != OpEquals {
		return fmt.Errorf("metadata fields only support = operator")
	}
	key := strings.TrimPrefix(comp.Field, "metadata.")
	if err := storage.ValidateMetadataKey(key); err != nil {
		return err
	}
	if filter.MetadataFields == nil {
		filter.MetadataFields = make(map[string]string)
	}
	filter.MetadataFields[key] = comp.Value
	return nil
}

// applyHasMetadataKeyFilter handles has_metadata_key=<keyname> queries (GH#1406).
func (e *Evaluator) applyHasMetadataKeyFilter(comp *ComparisonNode, filter *types.IssueFilter) error {
	if comp.Op != OpEquals {
		return fmt.Errorf("has_metadata_key only supports = operator")
	}
	if err := storage.ValidateMetadataKey(comp.Value); err != nil {
		return err
	}
	filter.HasMetadataKey = comp.Value
	return nil
}

// buildMetadataPredicate builds a predicate for metadata.<key>=<value> in OR queries.
// Parses the issue's JSON metadata and compares the top-level scalar at the given key.
func (e *Evaluator) buildMetadataPredicate(comp *ComparisonNode) (func(*types.Issue) bool, error) {
	if comp.Op != OpEquals {
		return nil, fmt.Errorf("metadata fields only support = operator")
	}
	key := strings.TrimPrefix(comp.Field, "metadata.")
	if err := storage.ValidateMetadataKey(key); err != nil {
		return nil, err
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Use `has_metadata_key=team` — the only supported form
  2. 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)
  3. In OR/predicate mode the equivalent restriction applies (see buildHasMetadataKeyPredicate); keep the same = form there
  4. Match the key name exactly as stored; keys are validated by storage.ValidateMetadataKey

Example fix

// before
bd list 'has_metadata_key!=team'
// after
bd list 'has_metadata_key=team'
Defensive patterns

Strategy: validation

Validate before calling

if field == "has_metadata_key" && op != "=" {
    return fmt.Errorf("has_metadata_key requires = (got %s)", op)
}

Type guard

func isHasMetadataKeyEquals(field, op string) bool {
    return field == "has_metadata_key" && op == "="
}

Prevention

When it happens

Trigger: A query like `has_metadata_key!=team` or `has_metadata_key=~te` dispatched from applyComparison to applyHasMetadataKeyFilter with comp.Op != OpEquals.

Common situations: 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).

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/207d29affe8aff0b. Report an issue: GitHub.