Skip to content

[Device Groups] Two bugs: (1) dynamic group membership never persists after creation, causing the Groups list page to crash; (2) static group creation always fails — frontend sends filterConditions: null, which the backend schema rejects #3159

Description

@cisspUser01

Description

Two related but distinct bugs found while setting up Device Groups for the first time:

  1. 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.

  2. 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):

  1. 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" }
    ]}}
  2. Confirm the filter itself works correctly:
    POST /api/v1/groups/:id/preview?limit=20
    -> returns totalCount > 0 with real matching devices (confirmed working correctly)
  3. Check actual persisted membership:
    GET /api/v1/groups/:id/devices
    -> returns {"data":[],"total":0} — empty, despite preview confirming real matches
  4. Navigate to Devices > Groups in the dashboard.
  5. 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.
  6. 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):

  1. Devices > Groups > Create Device Group.
  2. Enter a name/description, select Group Type = Static, manually select devices to include.
  3. Click "Create group".
  4. 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"] }
    }
    }
  5. 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:

  1. 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)
  2. SELECT on device_groups.filter_conditions confirmed valid, schema-correct JSON
  3. 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)
  4. GET /api/v1/groups/:id/devices confirmed 0 persisted members
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions