{"record":{"id":"674f62e662875904","repo":"plandex-ai/plandex","slug":"error-marshalling-plans","errorCode":null,"errorMessage":"Error marshalling plans: ","messagePattern":"Error marshalling plans: ","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"app/server/handlers/plans_crud.go","lineNumber":311,"sourceCode":"func ListPlansHandler(w http.ResponseWriter, r *http.Request) {\n\tlog.Println(\"Received request for ListPlans\")\n\n\tauth := Authenticate(w, r, true)\n\tif auth == nil {\n\t\treturn\n\t}\n\n\tprojectIds := r.URL.Query()[\"projectId\"]\n\n\tlog.Println(\"projectIds: \", projectIds)\n\n\tvar apiPlans []*shared.Plan\n\n\twritePlans := func() {\n\t\tjsonBytes, err := json.Marshal(apiPlans)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error marshalling plans: %v\\n\", err)\n\t\t\thttp.Error(w, \"Error marshalling plans: \"+err.Error(), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tw.Write(jsonBytes)\n\t}\n\n\tif len(projectIds) == 0 {\n\t\twritePlans()\n\t\treturn\n\t}\n\n\tauthorizedProjectIds := []string{}\n\tfor _, projectId := range projectIds {\n\t\tif authorizeProjectOptional(w, projectId, auth, false) {\n\t\t\tauthorizedProjectIds = append(authorizedProjectIds, projectId)\n\t\t}\n\t}\n","sourceCodeStart":293,"sourceCodeEnd":329,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/handlers/plans_crud.go#L293-L329","documentation":"In ListPlansHandler's writePlans closure (plans_crud.go:311), json.Marshal fails to serialize the []*shared.Plan slice to JSON. Since Plan is a plain data struct, this is rare — it happens only if the struct (or something it references) contains an unserializable value such as a channel, func, or a cyclic reference, or a custom MarshalJSON that errors.","triggerScenarios":"json.Marshal(apiPlans) returns an error because a field added to shared.Plan is of an unsupported type (chan, func, complex), Plan.ToApi() populated such a field, or a custom MarshalJSON method on Plan/contained types returns an error.","commonSituations":"A developer adds a non-serializable field (e.g. a done channel or callback) to the shared Plan struct without a json tag or custom marshaller; introducing a reference cycle via pointers.","solutions":["Inspect the logged marshal error — it names the unsupported type/field; add a json:\"-\" tag or remove the field from the API struct.","Ensure ToApi() returns a dedicated API DTO containing only JSON-safe primitives.","Implement MarshalJSON on the offending type if it must be represented differently.","Add a unit test marshalling an example Plan to catch regressions early."],"exampleFix":"// before\ntype Plan struct {\n    Id     string\n    DoneCh chan struct{} // unsupported\n}\n// after\ntype Plan struct {\n    Id     string `json:\"id\"`\n    DoneCh chan struct{} `json:\"-\"`\n}","handlingStrategy":"type-guard","validationCode":"// in tests: ensure API plans are always JSON-safe\nif err := json.Marshal(plan.ToApi()); err != nil {\n    t.Fatalf(\"Plan is not JSON-serializable: %v\", err)\n}","typeGuard":"func isJSONSafe(v any) bool {\n    switch v.(type) {\n    case chan struct{}, func(), map[string]chan int, complex128:\n        return false\n    }\n    return json.NewValidator != nil // fall back to json.Valid on marshalled bytes\n}\n// simpler: marshal and check\nfunc jsonSafe(b []byte) bool { return json.Valid(b) }","tryCatchPattern":"jsonBytes, err := json.Marshal(apiPlans)\nif err != nil {\n    log.Printf(\"Error marshalling plans: %v\", err)\n    http.Error(w, \"marshal failure\", http.StatusInternalServerError)\n    return\n}","preventionTips":["Tag internal fields (channels, funcs, conns) with json:\"-\" in shared structs.","Return a dedicated API DTO from ToApi() containing only JSON-safe primitives.","Add a serialization unit test for Plan that runs in CI.","Never expose raw domain structs over HTTP."],"tags":["json","go","serialization","http-500"],"backgroundTag":"json-marshalling-failed","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}