Description
Two related but distinct bugs found while setting up Device Groups for the first time:
-
Creating a DYNAMIC group with a valid filter correctly matches devices via the preview
endpoint, but the actual membership is never persisted — the group shows 0 members via both
the UI and the API, indefinitely. The Devices > Groups list page then crashes with an
uncaught TypeError whenever a group in this state is present, rendering a blank page.
-
Creating a STATIC group fails outright with a 400 — the web dashboard's Create Device Group
form always sends "filterConditions": null in the request body, even when Group Type is set
to Static, and the backend validation schema rejects null (expects either a real object or
the field to be omitted entirely).
Steps to Reproduce
Bug 1 (dynamic group membership):
- Create a dynamic group with a valid filter, e.g.: POST /api/v1/groups
{ "orgId": "...", "name": "TEST-GROUP", "type": "dynamic",
"filterConditions": { "operator": "AND", "conditions": [
{ "field": "hostname", "operator": "contains", "value": "SOME-PREFIX" }
]}}
- Confirm the filter itself works correctly:
POST /api/v1/groups/:id/preview?limit=20
-> returns totalCount > 0 with real matching devices (confirmed working correctly)
- Check actual persisted membership:
GET /api/v1/groups/:id/devices
-> returns {"data":[],"total":0} — empty, despite preview confirming real matches
- Navigate to Devices > Groups in the dashboard.
- Page renders blank. Browser console shows:
Uncaught TypeError: Cannot read properties of undefined (reading 'toLowerCase')
at DeviceGroupsPage.[hash].js:1:5696
(call stack: Array.filter -> Array.every -> Array.map chain, consistent with the page
iterating group/device data to build a sortable/filterable list)
Also observed in the same console session:
Failed to load resource: /api/v1/filters/preview... status 500 (x3)
— note this path does not match the documented /api/v1/groups/:id/preview endpoint.
- Deleting the affected group (DELETE /api/v1/groups/:id) and reloading was used to confirm the
page's crash is tied to a group in this broken state, not an unconditional page-load bug.
Bug 2 (static group creation):
- Devices > Groups > Create Device Group.
- Enter a name/description, select Group Type = Static, manually select devices to include.
- Click "Create group".
- Request fails: POST /api/v1/device-groups?orgId=... -> 400 Bad Request
Response body:
{
"error": "filterConditions: Invalid input: expected object, received null",
"details": {
"formErrors": [],
"fieldErrors": { "filterConditions": ["Invalid input: expected object, received null"] }
}
}
- This happens on every attempt, with a fresh browser session confirmed (full logout/login,
cache cleared) — ruled out as a session/auth issue; the request itself is malformed.
Expected Behavior
Bug 1: After a dynamic group is created (or its filter updated), membership evaluation should
run and populate device_group_memberships to match what the preview endpoint already correctly
computes — per the docs: "When a dynamic group is created or its filter is updated, the system
evaluates the filter against all devices in the organization and automatically adds or removes
members." The Groups list page should render group cards regardless of a group's membership
count (including zero), rather than crashing the whole page.
Bug 2: Creating a static group via the dashboard should succeed. Per the documented API example,
a static group's create payload omits filterConditions entirely:
{ "orgId": "ORG_UUID", "name": "Executive Laptops", "type": "static" }
The frontend should either omit this field for static groups or send {} — not null.
Actual Behavior
Bug 1:
- Membership evaluation appears to never run (or fails silently) on group creation — confirmed
by comparing preview (totalCount: 6, real matching devices returned) against the actual
persisted membership (GET .../devices returns empty, 0 total) for the same group, same filter,
same moment in time.
- The Devices > Groups list page crashes entirely (blank page) rather than gracefully rendering
a group with 0 (or otherwise unusual) membership state.
- Ruled out during diagnosis: the group's own data is clean (valid name, valid filter_conditions
JSON matching the documented schema, filter_fields_used correctly cached); all devices in the
environment have valid, non-null hostnames; the specific devices the filter should match are
confirmed real, online, and correctly evaluated by the preview endpoint.
Bug 2:
- The web form always includes "filterConditions": null in the POST body regardless of the
selected Group Type, causing every static group creation attempt to fail validation.
- Confirmed not an auth/session issue: reproduced identically after a full explicit logout,
cache clear, and fresh login with MFA.
Component
Web Dashboard
Version
Web/API 0.103.0, self-hosted via Docker Compose
Relevant Logs
Bug 1 — browser console (Devices > Groups page load):
Uncaught TypeError: Cannot read properties of undefined (reading 'toLowerCase')
at DeviceGroupsPage.BhbJr109.js:1:5696
at DeviceGroupsPage.BhbJr109.js:1:6297
at Array.every (<anonymous>)
at DeviceGroupsPage.BhbJr109.js:1:6288
at Array.filter (<anonymous>)
at DeviceGroupsPage.BhbJr109.js:1:6276
at DeviceGroupsPage.BhbJr109.js:1:6391
at DeviceGroupsPage.BhbJr109.js:1:13222
at Array.map (<anonymous>)
at DeviceGroupsPage.BhbJr109.js:1:13208
Failed to load resource: the server responded with a status of 500
/api/v1/filters/preview... (x3, path does not match documented /api/v1/groups/:id/preview)
Bug 2 — Network tab (Create Device Group submit):
POST https://rmm.nglantz.com/api/v1/device-groups?orgId=<org-id> 400 (Bad Request)
{"error":"filterConditions: Invalid input: expected object, received null","details":{"formErrors":[],"fieldErrors":{"filterConditions":["Invalid input: expected object, received null"]}}}
Additional Context
Diagnostic sequence used to isolate Bug 1 to the membership-persistence layer specifically:
- SELECT on device_groups confirmed clean group record (valid name, org_id; site_id was NULL,
tested assigning a site — did not resolve the page crash, ruling that out as a cause)
- SELECT on device_groups.filter_conditions confirmed valid, schema-correct JSON
- SELECT on devices confirmed no NULL/empty hostnames anywhere in the fleet (display_name is
frequently NULL, which is documented as optional/expected and appears unrelated)
- GET /api/v1/groups/:id/devices confirmed 0 persisted members
- POST /api/v1/groups/:id/preview confirmed the filter correctly matches 6 real, online devices
at the exact moment membership shows empty — proving filter logic is not the issue
Bug 2 was found immediately after while attempting a Static group as an alternative approach —
same underlying goal (grouping the NGCLUSTHOST-* cluster hosts), different group type, different
and unrelated failure mode.
Workaround in use for Bug 2: creating static groups directly via API, bypassing the broken form:
POST /api/v1/groups { "orgId": "...", "name": "...", "type": "static" }
POST /api/v1/groups/:id/devices { "deviceIds": [...] }
This succeeds cleanly and matches the documented payload shape exactly.
Willing to provide additional API responses, screen recordings of either issue, or test against
a patch if one is proposed.
Description
Two related but distinct bugs found while setting up Device Groups for the first time:
Creating a DYNAMIC group with a valid filter correctly matches devices via the preview
endpoint, but the actual membership is never persisted — the group shows 0 members via both
the UI and the API, indefinitely. The Devices > Groups list page then crashes with an
uncaught TypeError whenever a group in this state is present, rendering a blank page.
Creating a STATIC group fails outright with a 400 — the web dashboard's Create Device Group
form always sends "filterConditions": null in the request body, even when Group Type is set
to Static, and the backend validation schema rejects null (expects either a real object or
the field to be omitted entirely).
Steps to Reproduce
Bug 1 (dynamic group membership):
{ "orgId": "...", "name": "TEST-GROUP", "type": "dynamic",
"filterConditions": { "operator": "AND", "conditions": [
{ "field": "hostname", "operator": "contains", "value": "SOME-PREFIX" }
]}}
POST /api/v1/groups/:id/preview?limit=20
-> returns totalCount > 0 with real matching devices (confirmed working correctly)
GET /api/v1/groups/:id/devices
-> returns {"data":[],"total":0} — empty, despite preview confirming real matches
Uncaught TypeError: Cannot read properties of undefined (reading 'toLowerCase')
at DeviceGroupsPage.[hash].js:1:5696
(call stack: Array.filter -> Array.every -> Array.map chain, consistent with the page
iterating group/device data to build a sortable/filterable list)
Also observed in the same console session:
Failed to load resource: /api/v1/filters/preview... status 500 (x3)
— note this path does not match the documented /api/v1/groups/:id/preview endpoint.
page's crash is tied to a group in this broken state, not an unconditional page-load bug.
Bug 2 (static group creation):
Response body:
{
"error": "filterConditions: Invalid input: expected object, received null",
"details": {
"formErrors": [],
"fieldErrors": { "filterConditions": ["Invalid input: expected object, received null"] }
}
}
cache cleared) — ruled out as a session/auth issue; the request itself is malformed.
Expected Behavior
Bug 1: After a dynamic group is created (or its filter updated), membership evaluation should
run and populate device_group_memberships to match what the preview endpoint already correctly
computes — per the docs: "When a dynamic group is created or its filter is updated, the system
evaluates the filter against all devices in the organization and automatically adds or removes
members." The Groups list page should render group cards regardless of a group's membership
count (including zero), rather than crashing the whole page.
Bug 2: Creating a static group via the dashboard should succeed. Per the documented API example,
a static group's create payload omits filterConditions entirely:
{ "orgId": "ORG_UUID", "name": "Executive Laptops", "type": "static" }
The frontend should either omit this field for static groups or send {} — not null.
Actual Behavior
Bug 1:
by comparing preview (totalCount: 6, real matching devices returned) against the actual
persisted membership (GET .../devices returns empty, 0 total) for the same group, same filter,
same moment in time.
a group with 0 (or otherwise unusual) membership state.
JSON matching the documented schema, filter_fields_used correctly cached); all devices in the
environment have valid, non-null hostnames; the specific devices the filter should match are
confirmed real, online, and correctly evaluated by the preview endpoint.
Bug 2:
selected Group Type, causing every static group creation attempt to fail validation.
cache clear, and fresh login with MFA.
Component
Web Dashboard
Version
Web/API 0.103.0, self-hosted via Docker Compose
Relevant Logs
Additional Context
Diagnostic sequence used to isolate Bug 1 to the membership-persistence layer specifically:
tested assigning a site — did not resolve the page crash, ruling that out as a cause)
frequently NULL, which is documented as optional/expected and appears unrelated)
at the exact moment membership shows empty — proving filter logic is not the issue
Bug 2 was found immediately after while attempting a Static group as an alternative approach —
same underlying goal (grouping the NGCLUSTHOST-* cluster hosts), different group type, different
and unrelated failure mode.
Workaround in use for Bug 2: creating static groups directly via API, bypassing the broken form:
POST /api/v1/groups { "orgId": "...", "name": "...", "type": "static" }
POST /api/v1/groups/:id/devices { "deviceIds": [...] }
This succeeds cleanly and matches the documented payload shape exactly.
Willing to provide additional API responses, screen recordings of either issue, or test against
a patch if one is proposed.