wavetermdev/waveterm · error

router cannot register %q route (invalid routeid)

Error message

router cannot register %q route (invalid routeid)

What it means

bindRouteLocally validates the routeId with isBindableRouteId before installing it in routeMap. Empty routeIds and routeIds in the reserved namespaces (ControlPrefix, RoutePrefix_Link) are not bindable, because those prefixes are used internally for control traffic and per-link routes; using them for a normal bind would shadow or collide with router internals.

Source

Thrown at pkg/wshutil/wshrouter.go:772

		return err
	}
	lm := router.getLinkMeta(linkId)
	if lm != nil {
		log.Printf("wshrouter unbind route %q from %s", routeId, lm.Name())
	}
	router.unannounceUpstream(routeId)
	if router.IsRootRouter() {
		router.unsubscribeFromBroker(routeId)
	}
	return nil
}

func (router *WshRouter) bindRouteLocally(linkId baseds.LinkId, routeId string, isSourceRoute bool) error {
	if linkId == baseds.NoLinkId {
		return fmt.Errorf("cannot bindroute %q to NoLinkId", routeId)
	}
	if !isBindableRouteId(routeId) {
		return fmt.Errorf("router cannot register %q route (invalid routeid)", routeId)
	}
	router.lock.Lock()
	defer router.lock.Unlock()
	lm := router.linkMap[linkId]
	if lm == nil {
		return fmt.Errorf("cannot bind route %q, no link with id %d found", routeId, linkId)
	}
	if !lm.trusted {
		return fmt.Errorf("cannot bind route %q, link %d is not trusted", routeId, linkId)
	}
	if isSourceRoute {
		if lm.linkKind != LinkKind_Leaf {
			return fmt.Errorf("cannot bind source route %q to link %d (link is not a leaf)", routeId, linkId)
		}
		if lm.sourceRouteId != "" && lm.sourceRouteId != routeId {
			return fmt.Errorf("cannot bind source route %q to link %d (link already has source route %q)", routeId, linkId, lm.sourceRouteId)
		}
		lm.sourceRouteId = routeId

View on GitHub (pinned to a4447c1563)

Solutions

  1. Verify the routeId is a non-empty named route that does not start with ControlPrefix or RoutePrefix_Link
  2. Fix the source of the routeId (remote announce payload, config value) so it produces a valid route name
  3. Pre-check with the same rules as isBindableRouteId before calling bindRoute

Example fix

// before
err := router.bindRoute(linkId, routeId, true) // routeId could be "" or "*link:..."
// after
if !isBindableRouteId(routeId) {
    return fmt.Errorf("skipping bind of reserved/empty routeid %q", routeId)
}
err := router.bindRoute(linkId, routeId, true)
Defensive patterns

Strategy: validation

Validate before calling

if routeId == "" || strings.HasPrefix(routeId, ControlPrefix) || strings.HasPrefix(routeId, RoutePrefix_Link) {
    return fmt.Errorf("invalid routeid %q for bind", routeId)
}
err := router.bindRoute(linkId, routeId, true)

Type guard

func bindableRouteId(s string) bool {
    return s != "" && !strings.HasPrefix(s, ControlPrefix) && !strings.HasPrefix(s, RoutePrefix_Link)
}
if !bindableRouteId(routeId) { return }

Try / catch

err := router.bindRoute(linkId, routeId, isSource)
if err != nil && strings.Contains(err.Error(), "invalid routeid") {
    // inspect the remote announce payload / config value that produced routeId
}

Prevention

When it happens

Trigger: Calling WshRouter.bindRoute / bindRouteLocally with routeId == "" or a routeId starting with the control prefix or the link-route prefix.

Common situations: An empty routeId from an unset config field or from a remote announce message with a blank route; accidentally rebinding an internal per-link route id; forwarding a routeannounce/routeunbind control message's route value into bindRoute without stripping internal prefixes.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/160c5a3a34c59233. Report an issue: GitHub.