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

  1. Route the verify request to the root: pass &wshrpc.RpcOpts{Route: wshutil.ControlRootRoute} in SendRpcRequest.
  2. Prefer calling AuthenticateTokenCommand (the non-verify wrapper) which itself forwards to the root when needed.
  3. 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

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

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/3288208e43ddad25. Report an issue: GitHub.