wavetermdev/waveterm · error

cannot bind source route %q to link %d (link already has sou

Error message

cannot bind source route %q to link %d (link already has source route %q)

What it means

A leaf link can own exactly one source route. When binding a source route, bindRouteLocally checks lm.sourceRouteId: if it is already set to a different routeId, the bind is refused to prevent a single leaf from claiming two source routes (which would make inbound routing ambiguous).

Source

Thrown at pkg/wshutil/wshrouter.go:788

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

View on GitHub (pinned to a4447c1563)

Solutions

  1. Unbind the existing source route (unbindRoute with the old routeId) before binding the new one
  2. If the leaf should keep its original route, bind with the existing lm.sourceRouteId instead of a new one
  3. Register a separate link per leaf route — one leaf link maps to one source route

Example fix

// before
err := router.bindRoute(linkId, newRouteId, true) // leaf already has oldRouteId
// after
if err := router.unbindRoute(linkId, oldRouteId); err != nil {
    return err
}
err := router.bindRoute(linkId, newRouteId, true)
Defensive patterns

Strategy: validation

Validate before calling

// one leaf link = one source route; unbind the old route before binding a new one
if oldRouteId != "" && oldRouteId != newRouteId {
    if err := router.unbindRoute(linkId, oldRouteId); err != nil {
        return err
    }
}
err := router.bindRoute(linkId, newRouteId, true)

Try / catch

err := router.bindRoute(linkId, routeId, true)
if err != nil && strings.Contains(err.Error(), "already has source route") {
    // unbind the existing source route or reuse it, then retry
}

Prevention

When it happens

Trigger: Calling WshRouter.bindRoute(linkId, routeId, true) on a leaf whose sourceRouteId is already bound to a different route — e.g. rebinding after a route rename, or reusing one registered leaf rpc for two routes.

Common situations: Re-registering/rebinding a leaf connection after reconnect with a new routeId while the old source route is still attached; sharing one AbstractRpcClient between two leaves; a stale routeannounce re-binding an old route onto a leaf that has moved on.

Related errors


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