{"record":{"id":"fb9026d7943b6d6a","repo":"multica-ai/multica","slug":"key-must-match-a-za-z-a-za-z0-9-0-63","errorCode":null,"errorMessage":"key must match ^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$","messagePattern":"key must match \\^\\[a-zA-Z_\\]\\[a-zA-Z0-9_\\.-\\](.+?)\\$","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"server/internal/handler/issue_metadata.go","lineNumber":50,"sourceCode":"\tmaxIssueMetadataKeys = 50\n)\n\nvar issueMetadataKeyRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$`)\n\n// SetIssueMetadataKeyRequest carries the JSON value to write under the key\n// named in the URL. Value is a RawMessage so we can preserve numeric vs.\n// string typing through to PostgreSQL — once decoded into `any`, JSON\n// numbers all collapse to float64 and we'd lose integer fidelity.\ntype SetIssueMetadataKeyRequest struct {\n\tValue json.RawMessage `json:\"value\"`\n}\n\nfunc validateIssueMetadataKey(key string) error {\n\tif key == \"\" {\n\t\treturn errors.New(\"key is required\")\n\t}\n\tif !issueMetadataKeyRE.MatchString(key) {\n\t\treturn errors.New(\"key must match ^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$\")\n\t}\n\treturn nil\n}\n\n// validateIssueMetadataValue rejects anything other than a primitive JSON\n// scalar. Null, arrays, and objects are not allowed — the V1 surface is\n// flat KV. Removing a key uses DELETE, not a null value.\nfunc validateIssueMetadataValue(raw json.RawMessage) error {\n\tif len(raw) == 0 {\n\t\treturn errors.New(\"value is required\")\n\t}\n\tvar v any\n\tif err := json.Unmarshal(raw, &v); err != nil {\n\t\treturn fmt.Errorf(\"value must be valid JSON: %w\", err)\n\t}\n\tswitch v.(type) {\n\tcase string, bool, float64:\n\t\treturn nil","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/handler/issue_metadata.go#L32-L68","documentation":"validateIssueMetadataKey rejects keys that do not match ^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$: they must start with a letter or underscore, may continue with letters, digits, underscore, dot, or hyphen, and be 1..64 characters total. This keeps metadata keys safe for use as JSONB object members and prevents injection-ish or unbounded key names. The error message helpfully embeds the exact regex so the caller can self-correct.","triggerScenarios":"PUT with key '123abc' (starts with digit), 'my key' (space), 'my/key' (slash), 'key!' (special char), 'a'.repeat(65) (too long), or '-flag' (starts with hyphen). Note dots and hyphens inside the key are fine.","commonSituations":"Using user-facing labels or free-text tags as metadata keys; embedding emails/URLs as keys ('user@x.com' fails on '@'); non-ASCII keys from localized UIs; long descriptive keys from LLM-generated metadata.","solutions":["Normalize keys before sending: lowercase, replace disallowed chars with '_' or '-', trim to 64 chars, prefix with '_' if it starts with a digit or hyphen.","Reserve the raw label as the metadata *value* and use a normalized slug as the key.","Apply the same regex client-side and show it as inline validation.","Reject non-ASCII input at the form layer or transliterate it."],"exampleFix":"// before\nconst key = label; // e.g. \"Due Date!\"\nawait fetch(`${base}/issues/${id}/metadata/${key}`, ...);\n\n// after\nconst key = label.trim().replace(/[^a-zA-Z0-9_.-]+/g, '_').replace(/^[^a-zA-Z_]/, '_$&').slice(0, 64);\nawait fetch(`${base}/issues/${id}/metadata/${encodeURIComponent(key)}`, ...);","handlingStrategy":"validation","validationCode":"const KEY_RE = /^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$/;\n\nfunction normalizeMetadataKey(raw) {\n  let k = String(raw ?? '').trim()\n    .replace(/[^a-zA-Z0-9_.-]+/g, '_')\n    .replace(/^[0-9-]/, '_$&')   // must start with letter or underscore\n    .slice(0, 64);\n  if (!KEY_RE.test(k)) throw new TypeError(`cannot normalize key: ${raw}`);\n  return k;\n}","typeGuard":"const isValidMetadataKey = (k) => typeof k === 'string' && /^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$/.test(k);","tryCatchPattern":null,"preventionTips":["Use slugs/normalized keys for storage; keep the human label as the value or in the UI layer only.","Show the regex as inline validation in the key input field.","Never derive keys from free text (emails, URLs, localized strings) without normalization."],"tags":["validation","metadata","regex","http-400"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}