wavetermdev/waveterm · error

failed to validate token: %w

Error message

failed to validate token: %w

What it means

During peer authentication, the incoming JobAccessToken presented by the connecting party is validated with wavejwt.ValidateAndExtract; if the JWT is invalid (bad signature, expired, malformed), this error wraps the JWT library error. The server rejects the connection before any job commands are allowed.

Source

Thrown at pkg/jobmanager/mainserverconn.go:75

		JobAuthToken: jobAuthToken,
	}
	err := wshclient.AuthenticateJobManagerCommand(msc.WshRpc, authData, &wshrpc.RpcOpts{Route: wshutil.ControlRoute})
	if err != nil {
		log.Printf("authenticateSelfToServer: failed to authenticate to server: %v\n", err)
		return fmt.Errorf("failed to authenticate to server: %w", err)
	}
	msc.SelfAuthenticated.Store(true)
	log.Printf("authenticateSelfToServer: successfully authenticated to server\n")
	return nil
}

func (msc *MainServerConn) AuthenticateToJobManagerCommand(ctx context.Context, data wshrpc.CommandAuthenticateToJobData) error {
	jobId, jobAuthToken := WshCmdJobManager.GetJobAuthInfo()

	claims, err := wavejwt.ValidateAndExtract(data.JobAccessToken)
	if err != nil {
		log.Printf("AuthenticateToJobManager: failed to validate token: %v\n", err)
		return fmt.Errorf("failed to validate token: %w", err)
	}
	if !claims.MainServer {
		log.Printf("AuthenticateToJobManager: MainServer claim not set\n")
		return fmt.Errorf("MainServer claim not set")
	}
	if claims.JobId != jobId {
		log.Printf("AuthenticateToJobManager: JobId mismatch: expected %s, got %s\n", jobId, claims.JobId)
		return fmt.Errorf("JobId mismatch")
	}
	msc.PeerAuthenticated.Store(true)
	log.Printf("AuthenticateToJobManager: authentication successful for JobId=%s\n", claims.JobId)

	err = msc.authenticateSelfToServer(jobAuthToken)
	if err != nil {
		msc.PeerAuthenticated.Store(false)
		return err
	}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Obtain a fresh JobAccessToken from the main server and retry the authentication.
  2. Verify both sides share the same JWT signing secret/key and that clocks are synced (NTP).
  3. Decode the wrapped error to see the JWT cause (expired vs signature) and fix accordingly.

Example fix

// before
claims, err := wavejwt.ValidateAndExtract(data.JobAccessToken)
// after
if claims, err := wavejwt.ValidateAndExtract(data.JobAccessToken); err != nil {
    return fmt.Errorf("token invalid (%v); request a fresh JobAccessToken from the main server", err)
} else { _ = claims }
Defensive patterns

Strategy: validation

Validate before calling

parts := strings.Split(data.JobAccessToken, ".")
if len(parts) != 3 {
    return fmt.Errorf("malformed JobAccessToken: must be a 3-part JWT")
}

Try / catch

if err := conn.AuthenticateToJobManagerCommand(ctx, authData); err != nil {
    if strings.Contains(err.Error(), "failed to validate token") {
        authData.JobAccessToken = fetchFreshToken()
        err = conn.AuthenticateToJobManagerCommand(ctx, authData)
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: AuthenticateToJobManagerCommand receives data.JobAccessToken that fails wavejwt.ValidateAndExtract — expired token, signed by the wrong key, or structurally malformed JWT.

Common situations: Stale access tokens after server key rotation, clock skew between machines causing premature expiry, copying an old token, or truncated tokens from manual copy/paste.

Related errors


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