wavetermdev/waveterm · error
cannot bind route %q to link %d (link is not a router)
Error message
cannot bind route %q to link %d (link is not a router)
What it means
When isSourceRoute is false, bindRouteLocally is binding an upstream/forwarded route, which must point at a link of kind LinkKind_Router (an intermediary that will forward traffic). If the link is not a router-kind link, the bind is rejected because a leaf link cannot forward routes for other endpoints.
Source
Thrown at pkg/wshutil/wshrouter.go:793
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
} else {
if lm.linkKind != LinkKind_Router {
return fmt.Errorf("cannot bind route %q to link %d (link is not a router)", routeId, linkId)
}
}
router.routeMap[routeId] = linkId
return nil
}
func (router *WshRouter) bindRoute(linkId baseds.LinkId, routeId string, isSourceRoute bool) error {
err := router.bindRouteLocally(linkId, routeId, isSourceRoute)
if err != nil {
return err
}
lm := router.getLinkMeta(linkId)
if lm != nil {
log.Printf("wshrouter bind route %q to %s", routeId, lm.Name())
}
// don't announce control routes upstream (they are local only)
if !strings.HasPrefix(routeId, ControlPrefix) {
router.announceUpstream(routeId)View on GitHub (pinned to a4447c1563)
Solutions
- Bind upstream routes only to links registered as routers (RegisterRouterLink with LinkKind_Router)
- For the leaf's own route, pass isSourceRoute=true (or use RegisterTrustedLeaf which does both)
- Verify the link's kind matches the intent of the bind call before invoking bindRoute
Example fix
// before linkId, _ := router.RegisterTrustedLeaf(rpc, routeId) // LinkKind_Leaf err := router.bindRoute(linkId, upstreamRouteId, false) // non-source bind on leaf // after routerLink := router.RegisterRouterLink(downstreamRpc) router.trustLink(routerLink, LinkKind_Router) err := router.bindRoute(routerLink, upstreamRouteId, false)
Defensive patterns
Strategy: validation
Validate before calling
// forwarded routes only through router-kind links routerLink := router.RegisterRouterLink(downstreamRpc) err := router.bindRoute(routerLink, routeId, false)
Try / catch
err := router.bindRoute(linkId, routeId, false)
if err != nil && strings.Contains(err.Error(), "not a router") {
// bind via a router-kind link, or pass isSourceRoute=true for the leaf's own route
} Prevention
- Keep upstream binds on router links and source binds on leaf links
- Double-check the isSourceRoute flag matches the link's role
- Review bind calls after any leaf<->router role migration
When it happens
Trigger: Calling WshRouter.bindRoute(linkId, routeId, false) against a leaf-kind link — e.g. trying to announce/bind an upstream route through a leaf connection registered via RegisterTrustedLeaf.
Common situations: Wiring an upstream route through a leaf connection instead of a downstream router link; mixing up argument order so isSourceRoute=false is used for the leaf's own route; after role changes (leaf<->router) the old bind calls are still made with the wrong flag.
Related errors
- cannot bind source route %q to link %d (link is not a leaf)
- invalid routeid %q
- cannot unbind %q to NoLinkId
- cannot bindroute %q to NoLinkId
- router cannot register %q route (invalid routeid)
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/80405ee43fb1ee8d.
Report an issue: GitHub.