{"record":{"id":"997603f8ae12b043","repo":"halo-dev/halo","slug":"target-parent-has-a-cyclic-parent-chain-997603","errorCode":null,"errorMessage":"Target parent has a cyclic parent chain.","messagePattern":"Target parent has a cyclic parent chain\\.","errorType":"validation","errorClass":"ServerWebInputException","httpStatus":400,"severity":"error","filePath":"application/src/main/java/run/halo/app/core/endpoint/console/MenuItemConsoleService.java","lineNumber":242,"sourceCode":"                .map(MenuItem.MenuItemSpec::getParent)\n                .orElse(null));\n    }\n\n    private static String menuNameOf(MenuItem menuItem) {\n        return Optional.ofNullable(menuItem.getSpec())\n                .map(MenuItem.MenuItemSpec::getMenuName)\n                .orElse(null);\n    }\n\n    private static boolean isDescendant(String candidateName, String ancestorName, Map<String, MenuItem> itemMap) {\n        var current = candidateName;\n        var visited = new HashSet<String>();\n        while (current != null) {\n            if (Objects.equals(current, ancestorName)) {\n                return true;\n            }\n            if (!visited.add(current)) {\n                throw new ServerWebInputException(\"Target parent has a cyclic parent chain.\");\n            }\n            current = Optional.ofNullable(itemMap.get(current))\n                    .map(MenuItemConsoleService::parentNameOf)\n                    .orElse(null);\n        }\n        return false;\n    }\n\n    private static List<MenuItem> siblings(List<MenuItem> items, String parentName, String excludingName) {\n        return items.stream()\n                .filter(item -> !Objects.equals(item.getMetadata().getName(), excludingName))\n                .filter(item -> Objects.equals(parentNameOf(item), parentName))\n                .sorted(defaultMenuItemComparator())\n                .collect(Collectors.toCollection(ArrayList::new));\n    }\n\n    private static int indexOf(List<MenuItem> menuItems, String name) {\n        for (int i = 0; i < menuItems.size(); i++) {","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/application/src/main/java/run/halo/app/core/endpoint/console/MenuItemConsoleService.java#L224-L260","documentation":"MenuItemConsoleService.isDescendant performs the same visited-set cycle detection as the category service, but for menu items (walking parent menu items via parentNameOf). It throws ServerWebInputException (HTTP 400) 'Target parent has a cyclic parent chain.' when the menu item hierarchy already loops, to prevent reparenting an item under its own descendant or operating on already-corrupted data.","triggerScenarios":"Reparenting a menu item such that the candidate parent chain revisits a node; operating on a menu whose items already form a loop due to prior corruption or racing writes.","commonSituations":"Concurrent menu restructuring requests racing; direct edits to menu item spec parent/priority creating a loop; import of menu data that contained a cycle.","solutions":["Inspect and repair the menu item hierarchy to remove any existing loop.","Do not reparent an item under one of its descendants.","Serialize menu hierarchy edits to avoid races forming cycles.","Validate imported menu data for cycles before applying."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if (isDescendant(newParentName, item.getMetadata().getName(), itemMap)) {\n    return Mono.error(new ServerWebInputException(\"Target parent has a cyclic parent chain.\"));\n}","typeGuard":"static boolean hasNoMenuItemCycle(Map<String, MenuItem> map, String start) {\n    var visited = new HashSet<String>();\n    var cur = start;\n    while (cur != null) {\n        if (!visited.add(cur)) return false;\n        cur = Optional.ofNullable(map.get(cur))\n            .map(MenuItemConsoleService::parentNameOf).orElse(null);\n    }\n    return true;\n}","tryCatchPattern":null,"preventionTips":["Serialize menu hierarchy edits to avoid races.","Never reparent a menu item under one of its descendants.","Validate imported menu data for cycles before applying."],"tags":["validation","menu-item","hierarchy","cycle-detection","http-400","data-integrity"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}