{"record":{"id":"c9f8f869ed186f12","repo":"wavetermdev/waveterm","slug":"no-routeid","errorCode":null,"errorMessage":"no routeid","messagePattern":"no routeid","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/wshutil/wshrouter_controlimpl.go","lineNumber":137,"sourceCode":"\n\treturn rtnData, nil\n}\n\nfunc extractTokenData(token string) (wshrpc.CommandAuthenticateRtnData, error) {\n\tentry := shellutil.GetAndRemoveTokenSwapEntry(token)\n\tif entry == nil {\n\t\treturn wshrpc.CommandAuthenticateRtnData{}, fmt.Errorf(\"no token entry found\")\n\t}\n\t_, err := validateRpcContextFromAuth(entry.RpcContext)\n\tif err != nil {\n\t\treturn wshrpc.CommandAuthenticateRtnData{}, err\n\t}\n\tif entry.RpcContext.IsRouter {\n\t\treturn wshrpc.CommandAuthenticateRtnData{}, fmt.Errorf(\"cannot auth router via token\")\n\t}\n\trouteId := entry.RpcContext.GenerateRouteId()\n\tif routeId == \"\" {\n\t\treturn wshrpc.CommandAuthenticateRtnData{}, fmt.Errorf(\"no routeid\")\n\t}\n\treturn wshrpc.CommandAuthenticateRtnData{\n\t\tRouteId:        routeId,\n\t\tEnv:            entry.Env,\n\t\tInitScriptText: entry.ScriptText,\n\t\tRpcContext:     entry.RpcContext,\n\t}, nil\n}\n\nfunc (impl *WshRouterControlImpl) AuthenticateTokenVerifyCommand(ctx context.Context, data wshrpc.CommandAuthenticateTokenData) (wshrpc.CommandAuthenticateRtnData, error) {\n\tif !impl.Router.IsRootRouter() {\n\t\treturn wshrpc.CommandAuthenticateRtnData{}, fmt.Errorf(\"authenticatetokenverify can only be called on root router\")\n\t}\n\tif data.Token == \"\" {\n\t\treturn wshrpc.CommandAuthenticateRtnData{}, fmt.Errorf(\"no token in authenticatetoken message\")\n\t}\n\n\trtnData, err := extractTokenData(data.Token)","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/wshutil/wshrouter_controlimpl.go#L119-L155","documentation":"After passing the IsRouter check, extractTokenData calls entry.RpcContext.GenerateRouteId() and requires a non-empty route id for leaf connections. An empty result means the stored RpcContext is a leaf that carries neither a RouteId nor a ProcRoute combination that produces a route id — the entry is effectively unusable for binding a route on the router.","triggerScenarios":"AuthenticateTokenCommand / AuthenticateTokenVerifyCommand with a token whose RpcContext has IsRouter=false but no RouteId (and no ProcRoute flag), so GenerateRouteId returns \"\".","commonSituations":"Hand-constructed RpcContext in custom token-minting scripts missing the RouteId field; a version mismatch where the minting side omits a field the verify side requires; JSON serialization dropping the field.","solutions":["Re-mint the token ensuring RpcContext.RouteId is set (or ProcRoute is used appropriately) before swapping.","Validate the RpcContext on the minting side with the same rules as validateRpcContextFromAuth (leaf must have a routeid).","Check that token serialization/deserialization between processes preserves the RouteId field (no omitempty mismatches)."],"exampleFix":"// before\nrpcCtx := &wshrpc.RpcContext{IsRouter: false} // no RouteId -> \"no routeid\"\n// after\nrpcCtx := &wshrpc.RpcContext{IsRouter: false, RouteId: \"conn-abc123\"}","handlingStrategy":"validation","validationCode":"// validate before minting, mirroring validateRpcContextFromAuth\nif !rpcCtx.IsRouter && rpcCtx.RouteId == \"\" && !rpcCtx.ProcRoute {\n    return fmt.Errorf(\"leaf RpcContext requires a RouteId or ProcRoute\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always set RouteId on leaf RpcContexts when minting tokens","Reuse the library's validateRpcContextFromAuth on the minting side","Check struct tags (omitempty) so serialization never drops RouteId"],"tags":["auth","token","routeid","validation"],"backgroundTag":"missing-route-id","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}