plandex-ai/plandex · error
Error marshalling plans:
Error message
Error marshalling plans:
What it means
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.
Source
Thrown at app/server/handlers/plans_crud.go:311
func ListPlansHandler(w http.ResponseWriter, r *http.Request) {
log.Println("Received request for ListPlans")
auth := Authenticate(w, r, true)
if auth == nil {
return
}
projectIds := r.URL.Query()["projectId"]
log.Println("projectIds: ", projectIds)
var apiPlans []*shared.Plan
writePlans := func() {
jsonBytes, err := json.Marshal(apiPlans)
if err != nil {
log.Printf("Error marshalling plans: %v\n", err)
http.Error(w, "Error marshalling plans: "+err.Error(), http.StatusInternalServerError)
return
}
w.Write(jsonBytes)
}
if len(projectIds) == 0 {
writePlans()
return
}
authorizedProjectIds := []string{}
for _, projectId := range projectIds {
if authorizeProjectOptional(w, projectId, auth, false) {
authorizedProjectIds = append(authorizedProjectIds, projectId)
}
}
View on GitHub (pinned to e2d772072e)
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.
Example fix
// before
type Plan struct {
Id string
DoneCh chan struct{} // unsupported
}
// after
type Plan struct {
Id string `json:"id"`
DoneCh chan struct{} `json:"-"`
} Defensive patterns
Strategy: type-guard
Validate before calling
// in tests: ensure API plans are always JSON-safe
if err := json.Marshal(plan.ToApi()); err != nil {
t.Fatalf("Plan is not JSON-serializable: %v", err)
} Type guard
func isJSONSafe(v any) bool {
switch v.(type) {
case chan struct{}, func(), map[string]chan int, complex128:
return false
}
return json.NewValidator != nil // fall back to json.Valid on marshalled bytes
}
// simpler: marshal and check
func jsonSafe(b []byte) bool { return json.Valid(b) } Try / catch
jsonBytes, err := json.Marshal(apiPlans)
if err != nil {
log.Printf("Error marshalling plans: %v", err)
http.Error(w, "marshal failure", http.StatusInternalServerError)
return
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Error marshalling invites:
- Error marshalling contexts:
- Error marshalling response:
- Error marshalling response:
- Error marshalling projects:
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/674f62e662875904.
Report an issue: GitHub.