plandex-ai/plandex · error
Error marshalling users: {err.Error()}
Error message
Error marshalling users: {err.Error()} What it means
ListUsersHandler failed to serialize the ListUsersResponse (Users plus OrgUsersByUserId map) with encoding/json before writing the HTTP response. json.Marshal only errors on unsupported types (channels, funcs, cycles), so this almost always indicates a data-model defect, not a user mistake. The client receives the raw json error string appended to this message with HTTP 500.
Source
Thrown at app/server/handlers/users.go:84
http.Error(w, "Error listing org users: "+err.Error(), http.StatusInternalServerError)
return
}
orgUsersByUserId := make(map[string]*shared.OrgUser)
for _, orgUser := range orgUsers {
orgUsersByUserId[orgUser.UserId] = orgUser.ToApi()
}
resp := shared.ListUsersResponse{
Users: apiUsers,
OrgUsersByUserId: orgUsersByUserId,
}
bytes, err := json.Marshal(resp)
if err != nil {
log.Println("Error marshalling users: ", err)
http.Error(w, "Error marshalling users: "+err.Error(), http.StatusInternalServerError)
return
}
log.Println("Successfully processed request for ListUsersHandler")
w.Write(bytes)
}
func DeleteOrgUserHandler(w http.ResponseWriter, r *http.Request) {
log.Println("Received a request for DeleteOrgUserHandler")
if os.Getenv("GOENV") == "development" && os.Getenv("LOCAL_MODE") == "1" {
writeApiError(w, shared.ApiError{
Type: shared.ApiErrorTypeOther,
Status: http.StatusForbidden,
Msg: "Local mode is not supported for user management",
})
returnView on GitHub (pinned to e2d772072e)
Solutions
- Inspect the server log line 'Error marshalling users: ' for the exact json.UnsupportedTypeError / UnsupportedValueError and field path
- Remove or wrap unsupported field types (chan, func, complex) on shared.ListUsersResponse and the structs produced by ToApi()
- Break reference cycles in the response structs by copying values instead of pointers back to parent objects
- Add a unit test that marshals a fully populated ListUsersResponse fixture
Example fix
// before
type User struct {
Callback func()
}
// after
type User struct {
CallbackName string
} Defensive patterns
Strategy: validation
Validate before calling
if err := json.NewEncoder(io.Discard).Encode(resp); err != nil {
return fmt.Errorf("response not serializable: %w", err)
} Type guard
func isMarshalable(v any) bool {
return json.NewEncoder(io.Discard).Encode(v) == nil
} Try / catch
bytes, err := json.Marshal(resp)
if err != nil {
var ute *json.UnsupportedTypeError
if errors.As(err, &ute) {
log.Printf("unsupported type at field %s", ute.Type)
}
http.Error(w, "internal error", http.StatusInternalServerError)
return
} Prevention
- Keep API response structs limited to JSON-native types (string, number, bool, slice, map, nested structs)
- Add a marshalling smoke test for every response type in CI
- Never expose func/chan fields via ToApi() converters
- Use json.NewEncoder against io.Discard as a cheap pre-flight check in dev builds
When it happens
Trigger: json.Marshal(resp) returns an error — e.g. a field on a User/ToApi() struct contains a channel, func, or a reference cycle, or a custom MarshalJSON method panics/returns an error.
Common situations: Someone adds a field of type func, chan, or a cyclic pointer (e.g. *User pointing back to *Org) to the shared API structs; a custom Marshaler on ToApi() output returns an error on empty/edge-case data.
Related errors
- error marshalling models: %v
- error marshalling model pack: %v
- error marshalling current plan settings: %v
- error marshalling convo message: %v
- error marshalling convo message description: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/56c1baeebedbaef2.
Report an issue: GitHub.