wavetermdev/waveterm · error

cannot bindroute %q to NoLinkId

Error message

cannot bindroute %q to NoLinkId

What it means

bindRouteLocally maps a routeId onto a link in the router's routeMap. Since NoLinkId is the sentinel for 'no link', binding a route to it would create a corrupt routing entry, so the bind is rejected immediately as the first validation in the function.

Source

Thrown at pkg/wshutil/wshrouter.go:769

func (router *WshRouter) unbindRoute(linkId baseds.LinkId, routeId string) error {
	err := router.unbindRouteLocally(linkId, routeId)
	if err != nil {
		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 {

View on GitHub (pinned to a4447c1563)

Solutions

  1. Ensure the link is registered first and use the returned linkId from RegisterUntrustedLink/RegisterRouterLink
  2. Guard the bind call: skip or return early when linkId == baseds.NoLinkId
  3. Fix the lookup that yields NoLinkId (check error handling around link creation)

Example fix

// before
err := router.bindRoute(linkId, routeId, true)
// after
if linkId == baseds.NoLinkId {
    return fmt.Errorf("cannot bind route %q: link not registered yet", routeId)
}
err := router.bindRoute(linkId, routeId, true)
Defensive patterns

Strategy: validation

Validate before calling

if linkId == baseds.NoLinkId {
    return fmt.Errorf("link not registered; cannot bind %q", routeId)
}
err := router.bindRoute(linkId, routeId, true)

Type guard

func hasLink(id baseds.LinkId) bool { return id != baseds.NoLinkId }
if !hasLink(linkId) { return }

Try / catch

err := router.bindRoute(linkId, routeId, isSource)
if err != nil && strings.Contains(err.Error(), "NoLinkId") {
    // re-register the link, then retry the bind once
}

Prevention

When it happens

Trigger: Calling WshRouter.bindRoute / bindRouteLocally with linkId == baseds.NoLinkId — e.g. the link id came from an unset variable, a failed lookup, or a field that defaulted to zero before the link was registered.

Common situations: Registering a route during connection setup before RegisterUntrustedLink/RegisterRouterLink returned the real id; passing router.upstreamLinkId when no upstream is connected (it resets to NoLinkId after UnregisterLink); test code that forgets to register the link first.

Related errors


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