{"record":{"id":"199f1ced3a6fff88","repo":"multica-ai/multica","slug":"value-is-required","errorCode":null,"errorMessage":"value is required","messagePattern":"value is required","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"server/internal/handler/issue_metadata.go","lineNumber":60,"sourceCode":"\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\n\tcase nil:\n\t\treturn errors.New(\"value cannot be null (use DELETE to remove a key)\")\n\tdefault:\n\t\treturn errors.New(\"value must be a primitive: string, number, or bool\")\n\t}\n}\n\n// parseIssueMetadata decodes the JSONB bytes from db.Issue.Metadata into a\n// Go map suitable for response serialization. Empty or unparseable blobs\n// degrade to an empty map — the DB CHECK guarantees object shape, so this","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/handler/issue_metadata.go#L42-L78","documentation":"validateIssueMetadataValue rejects a metadata write whose JSON body has no `value` field content at all — len(raw) == 0 means the request either had an empty body or a literal JSON \"value\": null was collapsed to nothing by decoding (Go's json RawMessage keeps \"null\" as bytes, so in practice this is an absent/empty body or a decode upstream dropped it). The metadata V1 surface requires exactly one primitive value per write; it cannot represent 'no value'.","triggerScenarios":"PUT /issues/{id}/metadata/{key} with an empty request body, or a body of '{}' (no value key), or Content-Length 0 from a misconfigured client. Deletion has a dedicated DELETE verb, so an empty PUT is always a client bug.","commonSituations":"fetch POST/PUT with a body forgotten; client serializing {value: undefined} which JSON.stringify drops to '{}'; proxy stripping the body; using PUT with empty body intending deletion instead of DELETE.","solutions":["Always send a JSON body containing value: {\"value\": <string|number|bool>}.","Use DELETE /issues/{id}/metadata/{key} to remove a key — never an empty PUT.","Guard client-side: if (value === undefined) abort or default it explicitly.","Verify proxies/middleware are not consuming or dropping the request body before it reaches the handler."],"exampleFix":"// before\nawait fetch(url, {method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({})});\n\n// after\nawait fetch(url, {method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({value: 'some string'})});","handlingStrategy":"validation","validationCode":"async function putMetadata(url, value) {\n  if (value === undefined) throw new TypeError('metadata value is required (use DELETE to remove)');\n  const res = await fetch(url, {\n    method: 'PUT',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ value }),\n  });\n  if (!res.ok) throw new Error(`metadata write failed: ${res.status}`);\n}","typeGuard":"const hasPrimitiveValue = (body) => body !== null && typeof body === 'object' && 'value' in body && body.value !== undefined;","tryCatchPattern":null,"preventionTips":["Reject undefined values before serializing — JSON.stringify({value: undefined}) silently yields '{}'.","Use DELETE for removal; never send an empty-body PUT.","Verify test/proxy harnesses forward request bodies on PUT."],"tags":["validation","metadata","request-body","http-400"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}