halo-dev/halo · critical · Error

Cannot restore a replaced route under an anonymous parent re

Error message

Cannot restore a replaced route under an anonymous parent registered outside Halo's managed route setup.

What it means

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.

Source

Thrown at ui/src/setup/setupModules.ts:639

  routes: ReturnType<Router["getRoutes"]>,
  name: string | symbol
) {
  return routes.find((route) =>
    route.children?.some((child) => child.name === name)
  );
}

function restoreReplacedRoute(
  router: Router,
  replacedRoute: ReplacedRoute | undefined,
  routeOwners?: Map<string | symbol, UiProviderRegistration>,
  componentOwners?: WeakMap<object, UiProviderRegistration>
) {
  if (!replacedRoute) {
    return;
  }
  if (replacedRoute.nestedUnderAnonymousParent) {
    throw new Error(
      "Cannot restore a replaced route under an anonymous parent registered outside Halo's managed route setup."
    );
  }
  if (replacedRoute.parentName) {
    router.addRoute(replacedRoute.parentName, replacedRoute.route);
  } else {
    router.addRoute(replacedRoute.route);
  }
  if (replacedRoute.owner) {
    registerRouteOwners(
      replacedRoute.route,
      replacedRoute.owner,
      routeOwners,
      componentOwners
    );
  }
}

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. 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.
  2. Ensure any parent route involved in a replace is registered with an explicit name in Halo's managed setup, not an anonymous route.
  3. Update @halo-dev components/console to a version where the relevant parent routes carry stable names.

Example fix

// before (plugin module)
export default definePluginModule({
  routes: [{ parentName: "<unnamed-layout>", route: { name: "myPage", path: "page", component: Page } }]
});

// after
export default definePluginModule({
  routes: [{ name: "myPage", path: "my-plugin/page", component: Page }]
});
Defensive patterns

Strategy: validation

Validate before calling

// Plugin author guard: do not replace routes under unnamed parents.
import type { Router } from "vue-router";
function assertReplaceSafe(router: Router, parentName: string | symbol) {
  const parent = router.getRoutes().find((r) => r.name === parentName);
  if (!parent || !parent.name) {
    throw new Error(`Cannot replace under anonymous/unnamed parent ${String(parentName)}. Register a new named route instead.`);
  }
}

Type guard

function isNamedParent(route: { name?: unknown }): boolean {
  return route.name !== undefined && route.name !== null &&
    (typeof route.name === "string" || typeof route.name === "symbol");
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/6b7bc38b2f270b98. Report an issue: GitHub.