{"record":{"id":"4c05929881d4d0b6","repo":"wavetermdev/waveterm","slug":"cannot-bind-source-route-q-to-link-d-link-alrea","errorCode":null,"errorMessage":"cannot bind source route %q to link %d (link already has source route %q)","messagePattern":"cannot bind source route %q to link (.+?) \\(link already has source route %q\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/wshutil/wshrouter.go","lineNumber":788,"sourceCode":"\t}\n\tif !isBindableRouteId(routeId) {\n\t\treturn fmt.Errorf(\"router cannot register %q route (invalid routeid)\", routeId)\n\t}\n\trouter.lock.Lock()\n\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 {","sourceCodeStart":770,"sourceCodeEnd":806,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/wshutil/wshrouter.go#L770-L806","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Unbind the existing source route (unbindRoute with the old routeId) before binding the new one","If the leaf should keep its original route, bind with the existing lm.sourceRouteId instead of a new one","Register a separate link per leaf route — one leaf link maps to one source route"],"exampleFix":"// before\nerr := router.bindRoute(linkId, newRouteId, true) // leaf already has oldRouteId\n// after\nif err := router.unbindRoute(linkId, oldRouteId); err != nil {\n    return err\n}\nerr := router.bindRoute(linkId, newRouteId, true)","handlingStrategy":"validation","validationCode":"// one leaf link = one source route; unbind the old route before binding a new one\nif oldRouteId != \"\" && oldRouteId != newRouteId {\n    if err := router.unbindRoute(linkId, oldRouteId); err != nil {\n        return err\n    }\n}\nerr := router.bindRoute(linkId, newRouteId, true)","typeGuard":null,"tryCatchPattern":"err := router.bindRoute(linkId, routeId, true)\nif err != nil && strings.Contains(err.Error(), \"already has source route\") {\n    // unbind the existing source route or reuse it, then retry\n}","preventionTips":["Never share one AbstractRpcClient between multiple leaf routes","On reconnect, unbind the stale route before rebinding with a new routeId","Track each leaf's bound source route to avoid surprise conflicts"],"tags":["go","wshrouter","routing","conflict"],"backgroundTag":"route-already-bound","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}