louislam/uptime-kuma · error · Error

Invalid Monitor Group

Error message

Invalid Monitor Group

What it means

Thrown by 'editMonitor' when monitor.parent is not null AND that parent ID appears among the monitor's own descendants (Monitor.getAllChildrenIDs(monitor.id)). This prevents reparenting a monitor under one of its own descendants, which would create a cycle and an endless loop in tree traversal.

Source

Thrown at server/server.js:833

        });

        // Edit a monitor
        socket.on("editMonitor", async (monitor, callback) => {
            try {
                let removeGroupChildren = false;
                checkLogin(socket);

                let bean = await R.findOne("monitor", " id = ? ", [monitor.id]);

                if (bean.user_id !== socket.userID) {
                    throw new Error("Permission denied.");
                }

                // Check if Parent is Descendant (would cause endless loop)
                if (monitor.parent !== null) {
                    const childIDs = await Monitor.getAllChildrenIDs(monitor.id);
                    if (childIDs.includes(monitor.parent)) {
                        throw new Error("Invalid Monitor Group");
                    }
                }

                // Remove children if monitor type has changed (from group to non-group)
                if (bean.type === "group" && monitor.type !== bean.type) {
                    removeGroupChildren = true;
                }

                // Ensure status code ranges are strings
                if (!monitor.accepted_statuscodes.every((code) => typeof code === "string")) {
                    throw new Error("Accepted status codes are not all strings");
                }

                bean.name = monitor.name;
                bean.description = monitor.description;
                bean.parent = monitor.parent;
                bean.type = monitor.type;
                bean.subtype = monitor.subtype;

View on GitHub (pinned to 6b5ea01557)

Solutions

  1. Pick a parent that is not among the monitor's descendants (verify against getAllChildrenIDs before submitting).
  2. Disable descendant monitors in the parent-picker dropdown of the UI.
  3. If restructuring a subtree, move the would-be parent up the tree first to break the chain.
  4. Catch 'Invalid Monitor Group' and surface a clear message to re-select the parent.

Example fix

// before
socket.emit('editMonitor', { id, parent: candidateParent }, cb);

// after
const descendantIds = await getAllChildrenIDs(id);
if (descendantIds.includes(candidateParent)) {
  return error('Cannot set a descendant as parent');
}
socket.emit('editMonitor', { id, parent: candidateParent }, cb);
Defensive patterns

Strategy: validation

Validate before calling

// Reject cycle-creating parents before submitting
const descendantIds = await getAllChildrenIDs(monitor.id);
if (monitor.parent != null && descendantIds.includes(Number(monitor.parent))) {
  return setError('Cannot set a descendant monitor as the parent.');
}

Type guard

function isAcyclicParent(parentId, descendantIds) {
  return parentId == null || (!descendantIds.includes(Number(parentId)) && Number(parentId) !== Number(monitorId));
}

Prevention

When it happens

Trigger: Reorganizing monitor groups so that a parent monitor is moved beneath one of its current descendants. The cycle check fires before the edit is applied.

Common situations: Drag-and-drop group reordering that creates a cycle; bulk reparenting tool that does not check ancestry; UI that allows selecting any monitor as a parent.

Related errors


AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12). Data as JSON: /api/errors/be1f76a6c8c8a4f7. Report an issue: GitHub.