wavetermdev/waveterm · error
cannot unbind %q to NoLinkId
Error message
cannot unbind %q to NoLinkId
What it means
unbindRouteLocally removes a routeId->linkId mapping from the router's routeMap. A linkId of baseds.NoLinkId (0) is not a real link, so attempting to unbind a route 'to' NoLinkId indicates a caller bug — there is no link to unbind from — and the call is rejected before touching the map.
Source
Thrown at pkg/wshutil/wshrouter.go:741
if lm != nil {
log.Printf("wshrouter unregister link %s", lm.Name())
}
delete(router.linkMap, linkId)
if router.upstreamLinkId == linkId {
router.upstreamLinkId = baseds.NoLinkId
}
}
func isBindableRouteId(routeId string) bool {
if routeId == "" || strings.HasPrefix(routeId, ControlPrefix) || strings.HasPrefix(routeId, RoutePrefix_Link) {
return false
}
return true
}
func (router *WshRouter) unbindRouteLocally(linkId baseds.LinkId, routeId string) error {
if linkId == baseds.NoLinkId {
return fmt.Errorf("cannot unbind %q to NoLinkId", routeId)
}
router.lock.Lock()
defer router.lock.Unlock()
if router.routeMap[routeId] == linkId {
delete(router.routeMap, routeId)
}
return nil
}
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())
}View on GitHub (pinned to a4447c1563)
Solutions
- Check the linkId before calling unbindRoute; skip the unbind when linkId == baseds.NoLinkId
- Fix the upstream lookup that produced NoLinkId (e.g. ensure RegisterUntrustedLink/RegisterRouterLink ran before unbind)
- If unbinding by route name, resolve the current routeMap entry first and only unbind when a real link id exists
Example fix
// before
err := router.unbindRoute(linkId, routeId)
// after
if linkId == baseds.NoLinkId {
return nil // nothing bound; skip unbind
}
err := router.unbindRoute(linkId, routeId) Defensive patterns
Strategy: validation
Validate before calling
if linkId == baseds.NoLinkId {
return nil // or skip unbind
}
err := router.unbindRoute(linkId, routeId) Type guard
func hasLink(id baseds.LinkId) bool { return id != baseds.NoLinkId }
if !hasLink(linkId) { return } Try / catch
err := router.unbindRoute(linkId, routeId)
if err != nil && strings.Contains(err.Error(), "NoLinkId") {
// link was never registered; treat as no-op or re-resolve linkId
} Prevention
- Resolve linkId from the router's own lookup right before unbinding
- Skip unbind operations during teardown when links are already unregistered
- Don't cache linkIds across reconnects
When it happens
Trigger: Calling WshRouter.unbindRoute (or unbindRouteLocally) with linkId == baseds.NoLinkId, typically because a lookup like getLinkMeta/routeMap lookup returned NoLinkId and its result was passed through unchecked.
Common situations: Code that resolves a link id from a route or upstream value that was never registered (upstreamLinkId reset to NoLinkId after UnregisterLink); passing a zero-valued struct field as linkId; calling unbind during shutdown when the link was already unregistered.
Related errors
- invalid routeid %q
- cannot bindroute %q to NoLinkId
- router cannot register %q route (invalid routeid)
- cannot bind route %q, no link with id %d found
- cannot bind route %q, link %d is not trusted
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/1163d486d4c09445.
Report an issue: GitHub.