chenhg5/cc-connect · warning
invalid JSON:
Error message
invalid JSON:
What it means
The /send endpoint failed to decode the request body into SendRequest JSON. The raw decode error is appended after the prefix "invalid JSON: " and returned with 400. The body is read through io.LimitReader sized by sendBodyLimit(), so an over-large body can also produce a truncated/failed decode rather than a clean size error.
Source
Thrown at core/api.go:217
slog.Error("api server: write JSON failed", "error", err)
}
}
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 {View on GitHub (pinned to 4000b2338a)
Solutions
- Read the "invalid JSON: ..." suffix — it names the exact byte offset and syntax problem.
- Validate the payload: `jq . body.json` must succeed before sending; ensure Content-Type: application/json.
- If sending large attachments, check the base64-encoded size stays under the limit derived from maxAttachmentBytes; compress or lower quality if not.
- Ensure attachments are base64-encoded strings in the JSON, not raw binary multipart form fields.
Example fix
// before (shell)
curl -X POST http://127.0.0.1:8080/send -d message=hi
// after
curl -X POST http://127.0.0.1:8080/send -H 'Content-Type: application/json' -d '{"message":"hi"}' Defensive patterns
Strategy: validation
Validate before calling
if err := json.Unmarshal(payload, &map[string]any{}); err != nil {
return fmt.Errorf("payload is not valid JSON: %w", err)
}
if len(payload) > maxBodyLimit { return errors.New("payload too large") } Try / catch
if resp.StatusCode == 400 && strings.HasPrefix(body, "invalid JSON:") {
slog.Error("send rejected", "detail", body) // inspect offset in detail
} Prevention
- Always send Content-Type: application/json with a jq-validated body
- Base64-encode attachments and keep the encoded body under the configured limit
- Never post form-encoded or plain-text bodies to /send
When it happens
Trigger: POSTing to /send with a malformed JSON body, wrong Content-Type payload (form-encoded instead of JSON), an empty body, or a body exceeding sendBodyLimit() so the LimitReader truncates it mid-object.
Common situations: curl -d with unquoted/unescaped JSON on the shell, sending form data or plain text instead of application/json, attaching a file whose base64 form pushes the body past the configured attachment-derived limit, trailing commas in hand-written JSON.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- message, tts_text, or attachment is required
- hook input is not valid JSON
- usage endpoint returned status %d: %s
- decode usage response: %w
- reasonix: POST %s returned %d: %s
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/bdd2152c931ad749.
Report an issue: GitHub.