{"record":{"id":"82d0ee84e868eae5","repo":"gastownhall/beads","slug":"invalid-metadata-key-q-must-match-s","errorCode":null,"errorMessage":"invalid metadata key %q: must match %s","messagePattern":"invalid metadata key %q: must match (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/metadata.go","lineNumber":218,"sourceCode":"\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn errs\n}\n\n// validMetadataKeyRe validates metadata key names for use in JSON path expressions.\n// Allows alphanumeric, underscore, dot (dotted keys like \"jira.sprint\"), and\n// slash (path-style keys like \"jira/sprint\").\nvar validMetadataKeyRe = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_./]*$`)\n\n// ValidateMetadataKey checks that a metadata key is safe for use in JSON path\n// expressions. Keys must start with a letter or underscore and contain only\n// alphanumeric characters, underscores, dots, and slashes.\nfunc ValidateMetadataKey(key string) error {\n\tif !validMetadataKeyRe.MatchString(key) {\n\t\treturn fmt.Errorf(\"invalid metadata key %q: must match %s\", key, validMetadataKeyRe.String())\n\t}\n\treturn nil\n}\n\n// JSONMetadataPath returns a MySQL/Dolt JSON path expression for the given\n// metadata key. The key is always quoted so that \"gc.routed_to\" produces\n// '$.\"gc.routed_to\"' instead of '$.gc.routed_to' (which dolt interprets as a\n// nested path: {gc: {routed_to: ...}}); quoting is valid for plain keys too,\n// so no character list needs to stay in sync with validMetadataKeyRe. Slash\n// and mixed-case keys are proven to round-trip through the real Dolt/\n// go-mysql-server JSON path parser (see TestMetadataFilterSuite's\n// MetadataFieldMatchSlashKey and MetadataFieldMatchMixedCaseKey subtests in\n// cmd/bd/metadata_filter_test.go).\n//\n// Backslashes and quotes are also escaped, but every production caller\n// (sqlbuild.AppendMetadataClauses and doltTransaction.SearchIssues) validates\n// the key with ValidateMetadataKey first, which rejects `\"` and `\\`, so those escaping\n// branches are unreachable in practice today and are exercised only by the","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/metadata.go#L200-L236","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nedits := []MetadataEdit{{Key: \"gc-routed_to\", Value: ...}} // hyphen rejected\n// after\nedits := []MetadataEdit{{Key: \"gc.routed_to\", Value: ...}}","handlingStrategy":"validation","validationCode":"var validMetadataKeyRe = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_./]*$`)\nif !validMetadataKeyRe.MatchString(key) {\n    return fmt.Errorf(\"key %q rejected; use [a-zA-Z0-9_./] starting with letter or underscore\", key)\n}","typeGuard":"func isValidMetadataKey(key string) bool {\n    return regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_./]*$`).MatchString(key)\n}","tryCatchPattern":"if err := ValidateMetadataKey(key); err != nil {\n    // Sanitize before retrying: replace invalid chars.\n    key = regexp.MustCompile(`[^a-zA-Z0-9_./]`).ReplaceAllString(key, \"_\")\n    if err := ValidateMetadataKey(key); err != nil { return err }\n}","preventionTips":["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"],"tags":["go","metadata","key-validation","json-path","injection-guard"],"backgroundTag":"invalid-metadata-key","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}