{"record":{"id":"adfa5c1d41520c93","repo":"chenhg5/cc-connect","slug":"reasonix-marshal-body-w","errorCode":null,"errorMessage":"reasonix: marshal body: %w","messagePattern":"reasonix: marshal body: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/reasonix/session.go","lineNumber":479,"sourceCode":"func (s *reasonixSession) flushThinking() {\n\ts.mu.Lock()\n\ttext := s.thinkingBuf.String()\n\ts.thinkingBuf.Reset()\n\ts.mu.Unlock()\n\n\tif text == \"\" {\n\t\treturn\n\t}\n\ts.emit(core.Event{Type: core.EventThinking, Content: text})\n}\n\n// httpPost sends a JSON POST request to the reasonix serve endpoint.\nfunc (s *reasonixSession) httpPost(path string, body any) error {\n\tvar reqBody io.Reader\n\tif body != nil {\n\t\tdata, err := json.Marshal(body)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"reasonix: marshal body: %w\", err)\n\t\t}\n\t\treqBody = bytes.NewReader(data)\n\t}\n\n\treq, err := http.NewRequestWithContext(s.ctx, \"POST\", s.serveURL+path, reqBody)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"reasonix: create request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"reasonix: POST %s: %w\", path, err)\n\t}\n\tdefer func() {\n\t\tif err := resp.Body.Close(); err != nil {\n\t\t\tslog.Warn(\"reasonix: POST close body\", \"path\", path, \"error\", err)\n\t\t}","sourceCodeStart":461,"sourceCodeEnd":497,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/reasonix/session.go#L461-L497","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the wrapped error's json.MarshalTypeError field to find the offending Go type and field path","Ensure the body passed to httpPost is a plain struct or map[string]any of JSON-safe values","If options come from user config, sanitize/whitelist keys before building the request body"],"exampleFix":"// before: passing a body with a non-serializable field\nbody := map[string]any{\"opts\": s.opts} // opts contains a func\n// after: build an explicit JSON-safe struct\nbody := map[string]any{\"prompt\": prompt, \"session_id\": s.id}","handlingStrategy":"validation","validationCode":"// pre-flight: ensure the body serializes\nif b, err := json.Marshal(body); err != nil {\n    return fmt.Errorf(\"reasonix: invalid request body: %w\", err)\n} else { _ = b }","typeGuard":null,"tryCatchPattern":"// Go\nif err := sess.Send(prompt, id, imgs, files); err != nil {\n    var me *json.MarshalTypeError\n    if errors.As(err, &me) {\n        slog.Error(\"non-serializable body field\", \"field\", me.Field, \"type\", me.Type)\n    }\n}","preventionTips":["Pass only plain structs or map[string]any of JSON-safe values as request bodies","Never wrap funcs/channels into options maps","Add a unit test marshaling every agent request-body type"],"tags":["json","serialization","reasonix"],"backgroundTag":"json-marshal-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}