{"record":{"id":"80405ee43fb1ee8d","repo":"wavetermdev/waveterm","slug":"cannot-bind-route-q-to-link-d-link-is-not-a-rou","errorCode":null,"errorMessage":"cannot bind route %q to link %d (link is not a router)","messagePattern":"cannot bind route %q to link (.+?) \\(link is not a router\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/wshutil/wshrouter.go","lineNumber":793,"sourceCode":"\tdefer router.lock.Unlock()\n\tlm := router.linkMap[linkId]\n\tif lm == nil {\n\t\treturn fmt.Errorf(\"cannot bind route %q, no link with id %d found\", routeId, linkId)\n\t}\n\tif !lm.trusted {\n\t\treturn fmt.Errorf(\"cannot bind route %q, link %d is not trusted\", routeId, linkId)\n\t}\n\tif isSourceRoute {\n\t\tif lm.linkKind != LinkKind_Leaf {\n\t\t\treturn fmt.Errorf(\"cannot bind source route %q to link %d (link is not a leaf)\", routeId, linkId)\n\t\t}\n\t\tif lm.sourceRouteId != \"\" && lm.sourceRouteId != routeId {\n\t\t\treturn fmt.Errorf(\"cannot bind source route %q to link %d (link already has source route %q)\", routeId, linkId, lm.sourceRouteId)\n\t\t}\n\t\tlm.sourceRouteId = routeId\n\t} else {\n\t\tif lm.linkKind != LinkKind_Router {\n\t\t\treturn fmt.Errorf(\"cannot bind route %q to link %d (link is not a router)\", routeId, linkId)\n\t\t}\n\t}\n\trouter.routeMap[routeId] = linkId\n\treturn nil\n}\n\nfunc (router *WshRouter) bindRoute(linkId baseds.LinkId, routeId string, isSourceRoute bool) error {\n\terr := router.bindRouteLocally(linkId, routeId, isSourceRoute)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlm := router.getLinkMeta(linkId)\n\tif lm != nil {\n\t\tlog.Printf(\"wshrouter bind route %q to %s\", routeId, lm.Name())\n\t}\n\t// don't announce control routes upstream (they are local only)\n\tif !strings.HasPrefix(routeId, ControlPrefix) {\n\t\trouter.announceUpstream(routeId)","sourceCodeStart":775,"sourceCodeEnd":811,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/wshutil/wshrouter.go#L775-L811","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Bind upstream routes only to links registered as routers (RegisterRouterLink with LinkKind_Router)","For the leaf's own route, pass isSourceRoute=true (or use RegisterTrustedLeaf which does both)","Verify the link's kind matches the intent of the bind call before invoking bindRoute"],"exampleFix":"// before\nlinkId, _ := router.RegisterTrustedLeaf(rpc, routeId) // LinkKind_Leaf\nerr := router.bindRoute(linkId, upstreamRouteId, false) // non-source bind on leaf\n// after\nrouterLink := router.RegisterRouterLink(downstreamRpc)\nrouter.trustLink(routerLink, LinkKind_Router)\nerr := router.bindRoute(routerLink, upstreamRouteId, false)","handlingStrategy":"validation","validationCode":"// forwarded routes only through router-kind links\nrouterLink := router.RegisterRouterLink(downstreamRpc)\nerr := router.bindRoute(routerLink, routeId, false)","typeGuard":null,"tryCatchPattern":"err := router.bindRoute(linkId, routeId, false)\nif err != nil && strings.Contains(err.Error(), \"not a router\") {\n    // bind via a router-kind link, or pass isSourceRoute=true for the leaf's own route\n}","preventionTips":["Keep upstream binds on router links and source binds on leaf links","Double-check the isSourceRoute flag matches the link's role","Review bind calls after any leaf<->router role migration"],"tags":["go","wshrouter","routing","link-kind"],"backgroundTag":"wrong-link-kind","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}