gastownhall/beads · error

invalid metadata key filter

Error message

invalid metadata key filter: %w

What it means

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.

Solutions

  1. Read the wrapped ValidateMetadataKey error to see which rule the key breaks
  2. Normalize the key: trim whitespace, remove disallowed characters, match the allowed pattern/length
  3. If the intent is to filter on a field, verify the exact key with `bd show` on a known issue carrying that metadata

Example fix

// before
bd list --has-key "review status"
// after
bd list --has-key "review-status"
Defensive patterns

Strategy: validation

Validate before calling

if hasKey != "" {
	if err := storage.ValidateMetadataKey(hasKey); err != nil {
		return fmt.Errorf("--has-key: %w", err)
	}
}

Try / catch

err := ValidateMetadataFilters(fields, hasKey)
if err != nil && strings.HasPrefix(err.Error(), "invalid metadata key filter") {
	return fmt.Errorf("check --has-key value: %w", err)
}

Prevention

When it happens

Trigger: 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.

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

Related errors


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

Appendix: source

Thrown at internal/workapi/metadata.go:28

// ValidateMetadataFilters refuses a metadata key the query layer cannot spell.
//
// Both builders call it, so the leaf contract's "keys are validated inside" is
// true of every Reader implementation rather than of the CLI only. Before it
// existed the sole check lived in the SQL builder, whose error surfaced wrapped
// in the storage method's name — a shape nothing above storage can classify —
// so a typo'd key reached `bd list` as a usage error and the HTTP surface as a
// 500, on a parameter the frozen document promises a 400 for.
//
// The CLI still validates the same input at flag-parse time and still reports
// it in its own words: this is the floor under every caller, not a replacement
// for a front door's usage error.
//
// Keys are checked in sorted order so a request with two bad keys always names
// the same one.
func ValidateMetadataFilters(fields map[string]string, hasKey string) error {
	if hasKey != "" {
		if err := storage.ValidateMetadataKey(hasKey); err != nil {
			return fmt.Errorf("invalid metadata key filter: %w", err)
		}
	}
	keys := make([]string, 0, len(fields))
	for k := range fields {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		if err := storage.ValidateMetadataKey(k); err != nil {
			return fmt.Errorf("invalid metadata field key: %w", err)
		}
	}
	return nil
}

View on GitHub (pinned to 71377f2769)