gastownhall/beads · error
invalid metadata key %q: must match %s
Error message
invalid metadata key %q: must match %s
What it means
ValidateMetadataKey rejected a metadata key that does not match ^[a-zA-Z_][a-zA-Z0-9_./]*$. Keys are interpolated into MySQL/Dolt JSON path expressions, so keys must start with a letter or underscore and contain only alphanumerics, underscores, dots, and slashes; anything else (leading digit, spaces, quotes, backslashes, hyphens, unicode, empty string) is refused to keep the JSON path safe and unambiguous.
Source
Thrown at internal/storage/metadata.go:218
}
}
}
}
return errs
}
// validMetadataKeyRe validates metadata key names for use in JSON path expressions.
// Allows alphanumeric, underscore, dot (dotted keys like "jira.sprint"), and
// slash (path-style keys like "jira/sprint").
var validMetadataKeyRe = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_./]*$`)
// ValidateMetadataKey checks that a metadata key is safe for use in JSON path
// expressions. Keys must start with a letter or underscore and contain only
// alphanumeric characters, underscores, dots, and slashes.
func ValidateMetadataKey(key string) error {
if !validMetadataKeyRe.MatchString(key) {
return fmt.Errorf("invalid metadata key %q: must match %s", key, validMetadataKeyRe.String())
}
return nil
}
// JSONMetadataPath returns a MySQL/Dolt JSON path expression for the given
// metadata key. The key is always quoted so that "gc.routed_to" produces
// '$."gc.routed_to"' instead of '$.gc.routed_to' (which dolt interprets as a
// nested path: {gc: {routed_to: ...}}); quoting is valid for plain keys too,
// so no character list needs to stay in sync with validMetadataKeyRe. Slash
// and mixed-case keys are proven to round-trip through the real Dolt/
// go-mysql-server JSON path parser (see TestMetadataFilterSuite's
// MetadataFieldMatchSlashKey and MetadataFieldMatchMixedCaseKey subtests in
// cmd/bd/metadata_filter_test.go).
//
// Backslashes and quotes are also escaped, but every production caller
// (sqlbuild.AppendMetadataClauses and doltTransaction.SearchIssues) validates
// the key with ValidateMetadataKey first, which rejects `"` and `\`, so those escaping
// branches are unreachable in practice today and are exercised only by theView on GitHub (pinned to 71377f2769)
Solutions
- Rename the key to match the allowed pattern: start with a letter/underscore, use only [a-zA-Z0-9_./] (e.g. "my-key" → "my_key")
- Use dots or slashes for nesting-style keys ("jira.sprint", "jira/sprint") instead of hyphens
- Sanitize/normalize user-supplied keys before calling the API (strip or transliterate invalid characters)
- Check the quoted key in the error message for invisible characters like spaces or BOM
Example fix
// before
edits := []MetadataEdit{{Key: "gc-routed_to", Value: ...}} // hyphen rejected
// after
edits := []MetadataEdit{{Key: "gc.routed_to", Value: ...}} Defensive patterns
Strategy: validation
Validate before calling
var validMetadataKeyRe = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_./]*$`)
if !validMetadataKeyRe.MatchString(key) {
return fmt.Errorf("key %q rejected; use [a-zA-Z0-9_./] starting with letter or underscore", key)
} Type guard
func isValidMetadataKey(key string) bool {
return regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_./]*$`).MatchString(key)
} Try / catch
if err := ValidateMetadataKey(key); err != nil {
// Sanitize before retrying: replace invalid chars.
key = regexp.MustCompile(`[^a-zA-Z0-9_./]`).ReplaceAllString(key, "_")
if err := ValidateMetadataKey(key); err != nil { return err }
} Prevention
- Call ValidateMetadataKey yourself before building edits/filters to fail fast with better context
- Use dots or slashes for hierarchical keys ("jira.sprint"), never hyphens or spaces
- Sanitize user-supplied keys: strip non-[a-zA-Z0-9_./] characters and reject leading digits
- Remember the first character must be a letter or underscore — auto-generated numeric IDs need a prefix
When it happens
Trigger: Calling ApplyMetadataEdits or PlanCompareAndSetKey with keys like "1key", "my-key", "my key", "", "key[0]", or keys with quotes/backslashes; also any filter/metadata-update path that routes user-supplied keys through ValidateMetadataKey before JSONMetadataPath.
Common situations: Using hyphenated config-style keys ("gc-routed_to" vs the valid "gc.routed_to"); leading digits from auto-generated IDs; user input passed through un-sanitized; whitespace from trimmed-input mistakes.
Related errors
- ExternalDoltConfig: must set Socket or (Host, Port)
- db: GetMetadata %s: %w
- db: SetMetadata %s: %w
- db: GetLocalMetadata %s: %w
- db: RawSQL Query: columns: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/82d0ee84e868eae5.
Report an issue: GitHub.