wavetermdev/waveterm · error

cannot bind route %q, no link with id %d found

Error message

cannot bind route %q, no link with id %d found

What it means

After validating routeId, bindRouteLocally looks up the link in router.linkMap. If no link with the given linkId has been registered on this router, the bind cannot proceed because there is no connection to associate the route with. This is the router's 'unknown link' check.

Source

Thrown at pkg/wshutil/wshrouter.go:778

	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
	} 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

View on GitHub (pinned to a4447c1563)

Solutions

  1. Re-register the link (RegisterUntrustedLink/RegisterRouterLink) and use the freshly returned linkId
  2. Ensure the bind happens while the link is still live; reorder so bind occurs before any unregister/teardown
  3. Use the same WshRouter instance that owns the link; don't pass link ids across router instances

Example fix

// before
err := router.bindRoute(oldLinkId, routeId, true) // oldLinkId unregistered after reconnect
// after
linkId := router.RegisterUntrustedLink(rpc)
err := router.bindRoute(linkId, routeId, true)
Defensive patterns

Strategy: validation

Validate before calling

// bind only with a linkId this router returned and has not unregistered
linkId := router.RegisterUntrustedLink(rpc)
err := router.bindRoute(linkId, routeId, true)

Try / catch

err := router.bindRoute(staleLinkId, routeId, isSource)
if err != nil && strings.Contains(err.Error(), "no link with id") {
    // link was unregistered; re-register the link and retry the bind
}

Prevention

When it happens

Trigger: Calling WshRouter.bindRoute with a linkId that was never registered via RegisterUntrustedLink/RegisterRouterLink/RegisterTrustedLeaf, or that was already removed by UnregisterLink.

Common situations: Binding a route after the connection dropped and UnregisterLink ran; using a linkId from a different WshRouter instance; reusing a stale cached linkId across a reconnect; race between route binding and concurrent link unregistration.

Related errors


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