{"record":{"id":"7a98daff833dca6d","repo":"siyuan-note/siyuan","slug":"field-batch-must-not-be-empty","errorCode":null,"errorMessage":"Field [batch] must not be empty","messagePattern":"Field \\[batch\\] must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/clipboard_input.go","lineNumber":55,"sourceCode":"\t\t\t\t\treturn request, fmt.Errorf(\"Field [assets.%d.box] should be of type [String]\", i)\n\t\t\t\t}\n\t\t\t}\n\t\t\trequest.Assets = append(request.Assets, RichClipboardAsset{Index: int(index), Path: strings.TrimSpace(path), Box: strings.TrimSpace(box)})\n\t\t}\n\t\treturn\n\t}\n\tCleanupRichText.decodeRequest = func(reader io.Reader) (request CleanupRichTextRequest, err error) {\n\t\tfields, err := blockRequestFields(reader, \"/api/clipboard/cleanupRichText\")\n\t\tif err != nil {\n\t\t\treturn request, err\n\t\t}\n\t\trequest.Batch, err = legacyField[string](fields, \"batch\", \"String\", true)\n\t\tif err != nil {\n\t\t\treturn request, err\n\t\t}\n\t\trequest.Batch = strings.TrimSpace(request.Batch)\n\t\tif request.Batch == \"\" {\n\t\t\treturn request, fmt.Errorf(\"Field [batch] must not be empty\")\n\t\t}\n\t\tgroups, err := legacyField[[]json.RawMessage](fields, \"groups\", \"Array\", true)\n\t\tif err != nil {\n\t\t\treturn request, err\n\t\t}\n\t\trequest.Groups = make([]string, 0, len(groups))\n\t\tif len(groups) == 0 {\n\t\t\treturn request, fmt.Errorf(\"Field [groups] must not be empty\")\n\t\t}\n\t\tfor i, raw := range groups {\n\t\t\tvar group string\n\t\t\tif bytes.Equal(raw, []byte(\"null\")) || json.Unmarshal(raw, &group) != nil {\n\t\t\t\treturn request, fmt.Errorf(\"Field [groups.%d] should be of type [String]\", i)\n\t\t\t}\n\t\t\trequest.Groups = append(request.Groups, group)\n\t\t}\n\t\treturn\n\t}","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/clipboard_input.go#L37-L73","documentation":"The /api/clipboard/cleanupRichText decoder requires a batch identifier string that is non-empty after trimming. The batch ID ties this cleanup call to a prior prepareRichText session; an empty or whitespace-only batch is rejected because no cleanup target can be resolved.","triggerScenarios":"POSTing {\"batch\":\"\", \"groups\":[...]} or {\"batch\":\"   \", ...} to /api/clipboard/cleanupRichText, or building the payload before the batch ID from prepareRichText was captured.","commonSituations":"Calling cleanup without first storing the batch returned/used by prepare; a variable holding the batch ID is empty due to a failed prepare call that was ignored; passing an ID field with the wrong key name so the default empty string is sent.","solutions":["Pass the exact batch identifier string used/returned by the preceding prepareRichText call","Trim and check the batch value client-side before calling the API","Verify the prepare step actually succeeded and its batch ID was persisted before scheduling cleanup"],"exampleFix":"// before\nfetchPost(\"/api/clipboard/cleanupRichText\", {batch: batchId || \"\", groups})\n// after\nif (!batchId?.trim()) { return; }\nfetchPost(\"/api/clipboard/cleanupRichText\", {batch: batchId, groups})","handlingStrategy":"validation","validationCode":"if (typeof batch !== 'string' || !batch.trim()) throw new Error('batch must be a non-empty string');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Store the batch ID from prepareRichText before scheduling cleanup","Abort the cleanup flow if the prepare step failed","Trim the batch value before sending"],"tags":["validation","clipboard","request-body"],"backgroundTag":"empty-required-field","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}