{"record":{"id":"355165530257c66d","repo":"multica-ai/multica","slug":"value-cannot-be-empty-use-delete-to-unset-a-prope","errorCode":null,"errorMessage":"value cannot be empty (use DELETE to unset a property)","messagePattern":"value cannot be empty \\(use DELETE to unset a property\\)","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"server/internal/handler/property.go","lineNumber":323,"sourceCode":"\t\treturn nil, errors.New(\"value is required\")\n\t}\n\tvar v any\n\tif err := json.Unmarshal(raw, &v); err != nil {\n\t\treturn nil, fmt.Errorf(\"value must be valid JSON: %w\", err)\n\t}\n\tif v == nil {\n\t\treturn nil, errors.New(\"value cannot be null (use DELETE to unset a property)\")\n\t}\n\n\tcfg := parsePropertyConfig(def.Config)\n\tswitch def.Type {\n\tcase \"text\":\n\t\ts, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"value must be a string\")\n\t\t}\n\t\tif strings.TrimSpace(s) == \"\" {\n\t\t\treturn nil, errors.New(\"value cannot be empty (use DELETE to unset a property)\")\n\t\t}\n\t\tif utf8.RuneCountInString(s) > maxPropertyTextValueLen {\n\t\t\treturn nil, fmt.Errorf(\"value must be %d characters or fewer\", maxPropertyTextValueLen)\n\t\t}\n\t\treturn json.Marshal(sanitizeNullBytes(s))\n\tcase \"url\":\n\t\ts, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"value must be a URL string\")\n\t\t}\n\t\ts = strings.TrimSpace(s)\n\t\tif len(s) > maxPropertyURLValueLen {\n\t\t\treturn nil, fmt.Errorf(\"value must be %d characters or fewer\", maxPropertyURLValueLen)\n\t\t}\n\t\tu, err := url.Parse(s)\n\t\tif err != nil || (u.Scheme != \"http\" && u.Scheme != \"https\") || u.Host == \"\" {\n\t\t\treturn nil, errors.New(\"value must be an http(s) URL\")\n\t\t}","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/handler/property.go#L305-L341","documentation":"Thrown by validatePropertyValue in the Multica server when a property of type \"text\" is set to a string that is empty or only whitespace. The API treats JSON null as 'unset this property' and rejects empty strings instead of silently clearing them, so unsetting must go through the DELETE verb. It is returned as a 400-class validation error from the issue property update endpoint.","triggerScenarios":"PUT/PATCH on an issue property whose definition type is \"text\" with req.Value = \"\" or \"   \" (e.g. {\"value\": \"\"} in the property value update payload handled at property.go:762). Happens when a form submits an untouched-but-touched-and-cleared input, or when code trims a value to nothing before sending.","commonSituations":"Frontend forms that send the current field value on blur/save even when the user cleared it; import scripts mapping blank CSV cells to empty strings instead of null; a toggle from 'has value' to 'cleared' implemented as empty-string instead of DELETE.","solutions":["Send JSON null (or omit value) to clear the field, or use the DELETE endpoint for the property","Guard in the client: if the trimmed text is empty, issue DELETE instead of a value update","Keep a non-empty placeholder value if the property is required by workflow"],"exampleFix":"// before\nawait api.updateIssueProperty(issueId, propId, { value: \"\" });\n// after\nawait api.deleteIssueProperty(issueId, propId); // or send { value: null } per API contract","handlingStrategy":"validation","validationCode":"const v = req.value;\nif (typeof v === 'string' && v.trim() === '') {\n  // empty text means unset -> route to DELETE\n  await api.deleteIssueProperty(issueId, propId);\n  return;\n}","typeGuard":"function isEmptyTextValue(v: unknown): boolean {\n  return typeof v === 'string' && v.trim() === '';\n}","tryCatchPattern":"try { await api.updateIssueProperty(id, propId, { value }); }\ncatch (e) {\n  if (e.status === 400 && /cannot be empty/.test(e.message)) await api.deleteIssueProperty(id, propId);\n  else throw e;\n}","preventionTips":["Treat empty string and null differently in property forms: empty means DELETE","Trim-then-check before building the request payload","Map blank CSV/import cells to null, not ''"],"tags":["validation","issue-properties","api"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}