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 = routeIdView on GitHub (pinned to a4447c1563)
Solutions
- Verify the routeId is a non-empty named route that does not start with ControlPrefix or RoutePrefix_Link
- Fix the source of the routeId (remote announce payload, config value) so it produces a valid route name
- 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
- Validate routeIds from remote announce messages before binding them
- Reject empty route fields from config at load time
- Keep internal-prefixed route names out of any user-facing route config
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
- invalid routeid %q
- cannot unbind %q to NoLinkId
- cannot bindroute %q to NoLinkId
- cannot bind route %q, no link with id %d found
- cannot bind route %q, link %d is not trusted
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/160c5a3a34c59233.
Report an issue: GitHub.