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

  1. Bind upstream routes only to links registered as routers (RegisterRouterLink with LinkKind_Router)
  2. For the leaf's own route, pass isSourceRoute=true (or use RegisterTrustedLeaf which does both)
  3. 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

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


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