{"record":{"id":"ec1ade1e98acbe98","repo":"multica-ai/multica","slug":"key-is-required","errorCode":null,"errorMessage":"key is required","messagePattern":"key is required","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"server/internal/handler/issue_metadata.go","lineNumber":47,"sourceCode":"// any whole-blob overwrite would race with concurrent agent writes (see the\n// design discussion on MUL-2017).\nconst (\n\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}","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/handler/issue_metadata.go#L29-L65","documentation":"validateIssueMetadataKey rejects a metadata write whose URL-embedded key is the empty string. Issue metadata V1 is a flat key-value store addressed as PUT/DELETE .../metadata/{key}; an empty key means the request path ended in /metadata/ (or the router extracted nothing), which cannot name a value. The empty check runs before the pattern check and gives the distinct, actionable 'required' message.","triggerScenarios":"PUT /issues/{id}/metadata/ (trailing slash, empty key segment); client building the URL as `${base}/metadata/${key}` with key undefined/empty in JS; a route misconfiguration forwarding /metadata directly to the handler without a key capture group.","commonSituations":"Frontend form allowing submit with an empty key field; template string with an unset variable producing .../metadata/; proxy/gateway rewriting that strips the last path segment.","solutions":["Require a non-empty key in the client form/UI before enabling submit.","Check key truthiness before building the request URL.","Fix route wiring so the {key} path parameter is captured and non-empty (404/routing rather than handler-level empty).","Trim whitespace and reject empty-after-trim keys."],"exampleFix":"// before\nawait fetch(`${base}/issues/${id}/metadata/${key}`, {...}); // key may be ''\n\n// after\nif (!key || !key.trim()) throw new Error('metadata key is required');\nawait fetch(`${base}/issues/${id}/metadata/${encodeURIComponent(key.trim())}`, {...});","handlingStrategy":"validation","validationCode":"function metadataUrl(base, issueId, key) {\n  const k = String(key ?? '').trim();\n  if (!k) throw new TypeError('metadata key is required');\n  return `${base}/issues/${issueId}/metadata/${encodeURIComponent(k)}`;\n}","typeGuard":"const isNonEmptyKey = (k) => typeof k === 'string' && k.trim().length > 0;","tryCatchPattern":null,"preventionTips":["Disable the save button until the key field has non-whitespace content.","Keep key building in one URL helper so the empty check cannot be bypassed.","Encode the key with encodeURIComponent — spaces and slashes would otherwise corrupt the path."],"tags":["validation","metadata","path-params","http-400"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}