vxcontrol/pentagi · error

token invalid: %w

Error message

token invalid: %w

What it means

ValidateAPIToken falls through to "token invalid: %w" for any parse/verify error that is neither malformed nor expired/not-yet-valid. Typically this is a signature verification failure (wrong signing key) or a claims-validation error such as invalid subject/issuer or unusable claims.

Source

Thrown at backend/pkg/server/auth/api_token_jwt.go:53

	}
}

func ValidateAPIToken(tokenString, globalSalt string) (*models.APITokenClaims, error) {
	var claims models.APITokenClaims
	token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (any, error) {
		// verify signing algorithm to prevent "alg: none"
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return MakeJWTSigningKey(globalSalt), nil
	})
	if err != nil {
		if errors.Is(err, jwt.ErrTokenMalformed) {
			return nil, fmt.Errorf("token is malformed")
		} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
			return nil, fmt.Errorf("token is either expired or not active yet")
		} else {
			return nil, fmt.Errorf("token invalid: %w", err)
		}
	}

	if !token.Valid {
		return nil, fmt.Errorf("token is invalid")
	}

	return &claims, nil
}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Ensure GLOBAL_SALT (globalSalt) is identical across all server instances and unchanged since the token was issued; issue a new token after any salt change
  2. Inspect the wrapped cause (%w) to distinguish signature failure from claims failure
  3. Re-create the API token in the database/settings UI so it is signed with the current salt
  4. Verify only one installation/namespace is using the same database or token store

Example fix

// before
// .env on replica B differs
GLOBAL_SALT=different-salt
// after
// match the issuing server's salt on every replica
GLOBAL_SALT=original-salt
Defensive patterns

Strategy: try-catch

Type guard

func isSignatureError(err error) bool {
    return err != nil &&
        !strings.Contains(err.Error(), "malformed") &&
        !strings.Contains(err.Error(), "expired or not active yet") &&
        strings.Contains(err.Error(), "token invalid")
}

Try / catch

if err != nil {
    if strings.HasPrefix(err.Error(), "token invalid:") {
        // signature/key mismatch: re-issue token for this installation
        token = createTokenWithCurrentSalt()
    }
    return err
}

Prevention

When it happens

Trigger: Validating a token signed with a different globalSalt than the one configured on this server (MakeJWTSigningKey mismatch); token signed with RS/ES key rejected by the HMAC check surfaced as unexpected signing method wrapped here; corrupted token bytes that still decode.

Common situations: Server's GLOBAL_SALT env changed or differs between replicas/load balancers; token issued by a different PentAGI installation; database restored/shared across environments with different salts; a v3/v4 token format from an older version.

Understand the failure class

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/265a83fa5c10b734. Report an issue: GitHub.