chenhg5/cc-connect · warning

message, tts_text, or attachment is required

Error message

message, tts_text, or attachment is required

What it means

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.

Source

Thrown at core/api.go:221

func (s *APIServer) handleSend(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "POST only", http.StatusMethodNotAllowed)
		return
	}

	// Attachments travel base64-encoded inside the JSON body (~4/3 expansion)
	// plus the request envelope, so size the reader to fit one max-size
	// attachment with overhead to spare. The previous hard-coded 52 MB cap was
	// smaller than a single 50 MB attachment after base64 encoding and would
	// reject valid sends; deriving it from maxAttachmentBytes keeps the body
	// limit in step with the configured attachment limit.
	var req SendRequest
	if err := json.NewDecoder(io.LimitReader(r.Body, s.sendBodyLimit())).Decode(&req); err != nil {
		http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest)
		return
	}
	if req.Message == "" && strings.TrimSpace(req.TTSText) == "" && len(req.Images) == 0 && len(req.Files) == 0 && len(req.Audios) == 0 && len(req.Videos) == 0 {
		http.Error(w, "message, tts_text, or attachment is required", http.StatusBadRequest)
		return
	}

	s.mu.RLock()
	var engine *Engine
	var ok bool
	if req.Project != "" {
		engine, ok = s.engines[req.Project]
	} else if len(s.engines) == 1 {
		// No project specified and only one engine: use it by default.
		// Do NOT silently fall back when a non-empty project name is unknown —
		// that misroutes the message to the wrong engine. Mirrors the resolve
		// pattern in webhook.go and handleCronAdd.
		for _, e := range s.engines {
			engine = e
			ok = true
		}
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Include a non-empty field: `{"message":"hello"}` or `{"tts_text":"..."}` or at least one entry in images/files/audios/videos.
  2. Check the client isn't sending the text under a different key (the endpoint expects `message`, not `text` or `content`).
  3. Trim or reject whitespace-only messages on the caller side before sending.
  4. If the payload should have content, log the exact JSON body being posted — a failed template interpolation often yields empty strings.

Example fix

// before
{"message":"", "files":[]}
// after
{"message":"hello agent"}
Defensive patterns

Strategy: validation

Validate before calling

if req.Message == "" && req.TTSText == "" && len(req.Images) == 0 &&
    len(req.Files) == 0 && len(req.Audios) == 0 && len(req.Videos) == 0 {
    return errors.New("nothing to send: set message, tts_text, or an attachment")
}

Try / catch

if resp.StatusCode == 400 && strings.Contains(respBody, "message, tts_text, or attachment is required") {
    // fix payload construction before retrying
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/c27d077559f316a3. Report an issue: GitHub.