wavetermdev/waveterm · error
authenticatetokenverify can only be called on root router
Error message
authenticatetokenverify can only be called on root router
What it means
AuthenticateTokenVerifyCommand is the root-only control RPC that actually verifies token-swap entries. It refuses to run on any router that is not the root router (IsRootRouter() false), because only the root holds the token-swap store. The call must be routed to the ControlRootRoute so it lands on the root router.
Source
Thrown at pkg/wshutil/wshrouter_controlimpl.go:149
}
if entry.RpcContext.IsRouter {
return wshrpc.CommandAuthenticateRtnData{}, fmt.Errorf("cannot auth router via token")
}
routeId := entry.RpcContext.GenerateRouteId()
if routeId == "" {
return wshrpc.CommandAuthenticateRtnData{}, fmt.Errorf("no routeid")
}
return wshrpc.CommandAuthenticateRtnData{
RouteId: routeId,
Env: entry.Env,
InitScriptText: entry.ScriptText,
RpcContext: entry.RpcContext,
}, nil
}
func (impl *WshRouterControlImpl) AuthenticateTokenVerifyCommand(ctx context.Context, data wshrpc.CommandAuthenticateTokenData) (wshrpc.CommandAuthenticateRtnData, error) {
if !impl.Router.IsRootRouter() {
return wshrpc.CommandAuthenticateRtnData{}, fmt.Errorf("authenticatetokenverify can only be called on root router")
}
if data.Token == "" {
return wshrpc.CommandAuthenticateRtnData{}, fmt.Errorf("no token in authenticatetoken message")
}
rtnData, err := extractTokenData(data.Token)
if err != nil {
log.Printf("wshrouter authenticate-token-verify error: %v", err)
return wshrpc.CommandAuthenticateRtnData{}, err
}
log.Printf("wshrouter authenticate-token-verify success routeid=%q", rtnData.RouteId)
return rtnData, nil
}
func (impl *WshRouterControlImpl) AuthenticateTokenCommand(ctx context.Context, data wshrpc.CommandAuthenticateTokenData) (wshrpc.CommandAuthenticateRtnData, error) {
handler := GetRpcResponseHandlerFromContext(ctx)
if handler == nil {View on GitHub (pinned to a4447c1563)
Solutions
- Route the verify request to the root: pass &wshrpc.RpcOpts{Route: wshutil.ControlRootRoute} in SendRpcRequest.
- Prefer calling AuthenticateTokenCommand (the non-verify wrapper) which itself forwards to the root when needed.
- Check the router topology — if running inside wsh/ext processes, ensure the control connection reaches the Wave root router.
Example fix
// before
_, err := wshRpc.SendRpcRequest(wshrpc.Command_AuthenticateTokenVerify, data, nil) // lands on local router
// after
_, err := wshRpc.SendRpcRequest(wshrpc.Command_AuthenticateTokenVerify, data, &wshrpc.RpcOpts{Route: wshutil.ControlRootRoute}) Defensive patterns
Strategy: validation
Validate before calling
if !router.IsRootRouter() {
// don't call verify locally; forward to root
_, err = wshRpc.SendRpcRequest(ctx, wshrpc.Command_AuthenticateTokenVerify, data, &wshrpc.RpcOpts{Route: wshutil.ControlRootRoute})
} Prevention
- Always set Route: ControlRootRoute when sending AuthenticateTokenVerify
- Prefer the AuthenticateTokenCommand wrapper, which handles root/forwarding automatically
- Map your router topology to know which node is the root
When it happens
Trigger: Invoking Command_AuthenticateTokenVerify against a non-root (intermediate/proxy) WshRouter — e.g. an RPC routed to a local router instead of the root control route.
Common situations: Custom code calling the verify command directly on a child router/proxy; a chain of routers where the request terminated at an intermediate node rather than the root; misconfigured RpcOpts.Route not set to ControlRootRoute.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- no default route
- error setting up rpc client: %w
- cannot start remote job: no router available
- no route for %q
- error validating token: %w
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/3288208e43ddad25.
Report an issue: GitHub.