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 the

View on GitHub (pinned to 71377f2769)

Solutions

  1. 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")
  2. Use dots or slashes for nesting-style keys ("jira.sprint", "jira/sprint") instead of hyphens
  3. Sanitize/normalize user-supplied keys before calling the API (strip or transliterate invalid characters)
  4. 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

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


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