{"record":{"id":"be1f76a6c8c8a4f7","repo":"louislam/uptime-kuma","slug":"invalid-monitor-group","errorCode":null,"errorMessage":"Invalid Monitor Group","messagePattern":"Invalid Monitor Group","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/server.js","lineNumber":833,"sourceCode":"        });\n\n        // Edit a monitor\n        socket.on(\"editMonitor\", async (monitor, callback) => {\n            try {\n                let removeGroupChildren = false;\n                checkLogin(socket);\n\n                let bean = await R.findOne(\"monitor\", \" id = ? \", [monitor.id]);\n\n                if (bean.user_id !== socket.userID) {\n                    throw new Error(\"Permission denied.\");\n                }\n\n                // Check if Parent is Descendant (would cause endless loop)\n                if (monitor.parent !== null) {\n                    const childIDs = await Monitor.getAllChildrenIDs(monitor.id);\n                    if (childIDs.includes(monitor.parent)) {\n                        throw new Error(\"Invalid Monitor Group\");\n                    }\n                }\n\n                // Remove children if monitor type has changed (from group to non-group)\n                if (bean.type === \"group\" && monitor.type !== bean.type) {\n                    removeGroupChildren = true;\n                }\n\n                // Ensure status code ranges are strings\n                if (!monitor.accepted_statuscodes.every((code) => typeof code === \"string\")) {\n                    throw new Error(\"Accepted status codes are not all strings\");\n                }\n\n                bean.name = monitor.name;\n                bean.description = monitor.description;\n                bean.parent = monitor.parent;\n                bean.type = monitor.type;\n                bean.subtype = monitor.subtype;","sourceCodeStart":815,"sourceCodeEnd":851,"githubUrl":"https://github.com/louislam/uptime-kuma/blob/6b5ea0155793e666666745fb8d6fef1e829543a2/server/server.js#L815-L851","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pick a parent that is not among the monitor's descendants (verify against getAllChildrenIDs before submitting).","Disable descendant monitors in the parent-picker dropdown of the UI.","If restructuring a subtree, move the would-be parent up the tree first to break the chain.","Catch 'Invalid Monitor Group' and surface a clear message to re-select the parent."],"exampleFix":"// before\nsocket.emit('editMonitor', { id, parent: candidateParent }, cb);\n\n// after\nconst descendantIds = await getAllChildrenIDs(id);\nif (descendantIds.includes(candidateParent)) {\n  return error('Cannot set a descendant as parent');\n}\nsocket.emit('editMonitor', { id, parent: candidateParent }, cb);","handlingStrategy":"validation","validationCode":"// Reject cycle-creating parents before submitting\nconst descendantIds = await getAllChildrenIDs(monitor.id);\nif (monitor.parent != null && descendantIds.includes(Number(monitor.parent))) {\n  return setError('Cannot set a descendant monitor as the parent.');\n}","typeGuard":"function isAcyclicParent(parentId, descendantIds) {\n  return parentId == null || (!descendantIds.includes(Number(parentId)) && Number(parentId) !== Number(monitorId));\n}","tryCatchPattern":null,"preventionTips":["Compute descendants and exclude them (and self) from the parent picker.","Catch 'Invalid Monitor Group' and prompt the user to re-select a parent.","When restructuring subtrees, move parents up the tree first.","Validate ancestry client-side before emitting editMonitor."],"tags":["monitor","group","cycle-detection","tree","validation"],"backgroundTag":null,"analyzedSha":"6b5ea0155793e666666745fb8d6fef1e829543a2","analyzedAt":"2026-08-12T23:42:12.959Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}