{"record":{"id":"c27d077559f316a3","repo":"chenhg5/cc-connect","slug":"message-tts-text-or-attachment-is-required-c27d07","errorCode":null,"errorMessage":"message, tts_text, or attachment is required","messagePattern":"message, tts_text, or attachment is required","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"core/api.go","lineNumber":221,"sourceCode":"func (s *APIServer) handleSend(w http.ResponseWriter, r *http.Request) {\n\tif r.Method != http.MethodPost {\n\t\thttp.Error(w, \"POST only\", http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\n\t// Attachments travel base64-encoded inside the JSON body (~4/3 expansion)\n\t// plus the request envelope, so size the reader to fit one max-size\n\t// attachment with overhead to spare. The previous hard-coded 52 MB cap was\n\t// smaller than a single 50 MB attachment after base64 encoding and would\n\t// reject valid sends; deriving it from maxAttachmentBytes keeps the body\n\t// limit in step with the configured attachment limit.\n\tvar req SendRequest\n\tif err := json.NewDecoder(io.LimitReader(r.Body, s.sendBodyLimit())).Decode(&req); err != nil {\n\t\thttp.Error(w, \"invalid JSON: \"+err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\tif req.Message == \"\" && strings.TrimSpace(req.TTSText) == \"\" && len(req.Images) == 0 && len(req.Files) == 0 && len(req.Audios) == 0 && len(req.Videos) == 0 {\n\t\thttp.Error(w, \"message, tts_text, or attachment is required\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\ts.mu.RLock()\n\tvar engine *Engine\n\tvar ok bool\n\tif req.Project != \"\" {\n\t\tengine, ok = s.engines[req.Project]\n\t} else if len(s.engines) == 1 {\n\t\t// No project specified and only one engine: use it by default.\n\t\t// Do NOT silently fall back when a non-empty project name is unknown —\n\t\t// that misroutes the message to the wrong engine. Mirrors the resolve\n\t\t// pattern in webhook.go and handleCronAdd.\n\t\tfor _, e := range s.engines {\n\t\t\tengine = e\n\t\t\tok = true\n\t\t}\n\t}","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/api.go#L203-L239","documentation":"The /send endpoint requires at least one of: message text, tts_text, or an attachment (images, files, audios, videos). A syntactically valid JSON body whose SendRequest has all of these empty is rejected with 400, because there is nothing to deliver to the agent session.","triggerScenarios":"POSTing valid JSON like {} or {\"message\":\"\"} to /send where message is empty/whitespace, tts_text is empty, and images/files/audios/videos arrays are all empty.","commonSituations":"Client bug sending an empty payload after stripping content, double-encoding so the text ends up in a wrong field (e.g. nested under data), whitespace-only messages that fail the implicit emptiness check, a template that failed to interpolate its placeholder.","solutions":["Include a non-empty field: `{\"message\":\"hello\"}` or `{\"tts_text\":\"...\"}` or at least one entry in images/files/audios/videos.","Check the client isn't sending the text under a different key (the endpoint expects `message`, not `text` or `content`).","Trim or reject whitespace-only messages on the caller side before sending.","If the payload should have content, log the exact JSON body being posted — a failed template interpolation often yields empty strings."],"exampleFix":"// before\n{\"message\":\"\", \"files\":[]}\n// after\n{\"message\":\"hello agent\"}","handlingStrategy":"validation","validationCode":"if req.Message == \"\" && req.TTSText == \"\" && len(req.Images) == 0 &&\n    len(req.Files) == 0 && len(req.Audios) == 0 && len(req.Videos) == 0 {\n    return errors.New(\"nothing to send: set message, tts_text, or an attachment\")\n}","typeGuard":null,"tryCatchPattern":"if resp.StatusCode == 400 && strings.Contains(respBody, \"message, tts_text, or attachment is required\") {\n    // fix payload construction before retrying\n}","preventionTips":["Validate the payload client-side before posting","Use the exact field name `message` (not text/content)","Guard templates against interpolating to empty strings","Trim whitespace-only messages before sending"],"tags":["http","api","validation","empty-payload"],"backgroundTag":"empty-required-field","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}