chenhg5/cc-connect · error

reasonix: marshal body: %w

Error message

reasonix: marshal body: %w

What it means

httpPost marshals the request body to JSON before POSTing to the reasonix serve endpoint. If json.Marshal fails, it wraps the error as 'reasonix: marshal body: %w'. This should be nearly impossible for plain config structs but can occur for bodies containing channels, funcs, or cyclic references.

Source

Thrown at agent/reasonix/session.go:479

func (s *reasonixSession) flushThinking() {
	s.mu.Lock()
	text := s.thinkingBuf.String()
	s.thinkingBuf.Reset()
	s.mu.Unlock()

	if text == "" {
		return
	}
	s.emit(core.Event{Type: core.EventThinking, Content: text})
}

// httpPost sends a JSON POST request to the reasonix serve endpoint.
func (s *reasonixSession) httpPost(path string, body any) error {
	var reqBody io.Reader
	if body != nil {
		data, err := json.Marshal(body)
		if err != nil {
			return fmt.Errorf("reasonix: marshal body: %w", err)
		}
		reqBody = bytes.NewReader(data)
	}

	req, err := http.NewRequestWithContext(s.ctx, "POST", s.serveURL+path, reqBody)
	if err != nil {
		return fmt.Errorf("reasonix: create request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return fmt.Errorf("reasonix: POST %s: %w", path, err)
	}
	defer func() {
		if err := resp.Body.Close(); err != nil {
			slog.Warn("reasonix: POST close body", "path", path, "error", err)
		}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect the wrapped error's json.MarshalTypeError field to find the offending Go type and field path
  2. Ensure the body passed to httpPost is a plain struct or map[string]any of JSON-safe values
  3. If options come from user config, sanitize/whitelist keys before building the request body

Example fix

// before: passing a body with a non-serializable field
body := map[string]any{"opts": s.opts} // opts contains a func
// after: build an explicit JSON-safe struct
body := map[string]any{"prompt": prompt, "session_id": s.id}
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight: ensure the body serializes
if b, err := json.Marshal(body); err != nil {
    return fmt.Errorf("reasonix: invalid request body: %w", err)
} else { _ = b }

Try / catch

// Go
if err := sess.Send(prompt, id, imgs, files); err != nil {
    var me *json.MarshalTypeError
    if errors.As(err, &me) {
        slog.Error("non-serializable body field", "field", me.Field, "type", me.Type)
    }
}

Prevention

When it happens

Trigger: Calling newSession, Send, or RespondPermission where the constructed request body (any) contains a value json.Marshal cannot encode — e.g. a channel, function value, cyclic pointer graph, or an invalid type nested in the options map.

Common situations: A custom config option in map[string]any passed through from config.toml holds a non-serializable value; a plugin wraps the session and injects an exotic body type; a Go struct with unexported-only fields producing empty JSON is a related (non-error) smell.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


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