{"record":{"id":"6b7bc38b2f270b98","repo":"halo-dev/halo","slug":"cannot-restore-a-replaced-route-under-an-anonymous","errorCode":null,"errorMessage":"Cannot restore a replaced route under an anonymous parent registered outside Halo's managed route setup.","messagePattern":"Cannot restore a replaced route under an anonymous parent registered outside Halo's managed route setup\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"ui/src/setup/setupModules.ts","lineNumber":639,"sourceCode":"  routes: ReturnType<Router[\"getRoutes\"]>,\n  name: string | symbol\n) {\n  return routes.find((route) =>\n    route.children?.some((child) => child.name === name)\n  );\n}\n\nfunction restoreReplacedRoute(\n  router: Router,\n  replacedRoute: ReplacedRoute | undefined,\n  routeOwners?: Map<string | symbol, UiProviderRegistration>,\n  componentOwners?: WeakMap<object, UiProviderRegistration>\n) {\n  if (!replacedRoute) {\n    return;\n  }\n  if (replacedRoute.nestedUnderAnonymousParent) {\n    throw new Error(\n      \"Cannot restore a replaced route under an anonymous parent registered outside Halo's managed route setup.\"\n    );\n  }\n  if (replacedRoute.parentName) {\n    router.addRoute(replacedRoute.parentName, replacedRoute.route);\n  } else {\n    router.addRoute(replacedRoute.route);\n  }\n  if (replacedRoute.owner) {\n    registerRouteOwners(\n      replacedRoute.route,\n      replacedRoute.owner,\n      routeOwners,\n      componentOwners\n    );\n  }\n}\n","sourceCodeStart":621,"sourceCodeEnd":657,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/ui/src/setup/setupModules.ts#L621-L657","documentation":"Thrown by restoreReplacedRoute when a plugin that previously replaced a named route is being torn down/rolled back, but that replaced route was nested under an anonymous parent route (a parent with no name). Restoring requires re-adding the route under its parent by parentName; an anonymous parent has no name to target, so Halo cannot safely re-insert the route and aborts to avoid a corrupted route table. This is a host-side invariant: route replacement is only restorable when the parent route is named within Halo's managed setup.","triggerScenarios":"During plugin deactivation/uninstall rollback (the call site around line 523), restoreReplacedRoute receives a ReplacedRoute whose nestedUnderAnonymousParent is true. That flag was set in findReplacedRoute as Boolean(parent && !parent.name) — the existing route's parent in router.getRoutes() lacks a name.","commonSituations":"A plugin replaces a route whose parent is a wrapper/layout route registered without a name (anonymous Symbol-named parent created by assignInternalRouteParentNames only covers Halo-managed routes); a third-party router modification introduces an unnamed parent above a replaceable route; conflicting plugins that restructure the route tree before rollback.","solutions":["Avoid using route replacement (parentName append targeting a replace) for routes whose parent is not a Halo-named managed route; register a new named route instead.","Ensure any parent route involved in a replace is registered with an explicit name in Halo's managed setup, not an anonymous route.","Update @halo-dev components/console to a version where the relevant parent routes carry stable names."],"exampleFix":"// before (plugin module)\nexport default definePluginModule({\n  routes: [{ parentName: \"<unnamed-layout>\", route: { name: \"myPage\", path: \"page\", component: Page } }]\n});\n\n// after\nexport default definePluginModule({\n  routes: [{ name: \"myPage\", path: \"my-plugin/page\", component: Page }]\n});","handlingStrategy":"validation","validationCode":"// Plugin author guard: do not replace routes under unnamed parents.\nimport type { Router } from \"vue-router\";\nfunction assertReplaceSafe(router: Router, parentName: string | symbol) {\n  const parent = router.getRoutes().find((r) => r.name === parentName);\n  if (!parent || !parent.name) {\n    throw new Error(`Cannot replace under anonymous/unnamed parent ${String(parentName)}. Register a new named route instead.`);\n  }\n}","typeGuard":"function isNamedParent(route: { name?: unknown }): boolean {\n  return route.name !== undefined && route.name !== null &&\n    (typeof route.name === \"string\" || typeof route.name === \"symbol\");\n}","tryCatchPattern":null,"preventionTips":["Register your own named top-level route rather than replacing one under a layout wrapper.","Avoid route replacement (parentName append) in plugins; prefer standalone named routes.","Keep the Halo console and plugin SDK versions aligned so managed parent routes stay named."],"tags":["runtime","router","vue-router","plugin-activation","console"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}