{"record":{"id":"3f2f6dd1d93d71d4","repo":"puppetlabs/puppet","slug":"the-code-loaded-from-source-ref-does-not-seem-t-3f2f6d","errorCode":null,"errorMessage":"The code loaded from %{source_ref} does not seem to be a Puppet 3x API function - no 'newfunction' call.","messagePattern":"The code loaded from %(.+?) does not seem to be a Puppet 3x API function - no 'newfunction' call\\.","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb","lineNumber":25,"sourceCode":"class Puppet::Pops::Loader::RubyLegacyFunctionInstantiator\n  UNKNOWN = '<unknown>'\n\n  # Produces an instance of the Function class with the given typed_name, or fails with an error if the\n  # given ruby source does not produce this instance when evaluated.\n  #\n  # @param loader [Puppet::Pops::Loader::Loader] The loader the function is associated with\n  # @param typed_name [Puppet::Pops::Loader::TypedName] the type / name of the function to load\n  # @param source_ref [URI, String] a reference to the source / origin of the ruby code to evaluate\n  # @param ruby_code_string [String] ruby code in a string\n  #\n  # @return [Puppet::Pops::Functions.Function] - an instantiated function with global scope closure associated with the given loader\n  #\n  def self.create(loader, typed_name, source_ref, ruby_code_string)\n    # Assert content of 3x function by parsing\n    assertion_result = []\n    if assert_code(ruby_code_string, source_ref, assertion_result)\n      unless ruby_code_string.is_a?(String) && assertion_result.include?(:found_newfunction)\n        raise ArgumentError, _(\"The code loaded from %{source_ref} does not seem to be a Puppet 3x API function - no 'newfunction' call.\") % { source_ref: source_ref }\n      end\n    end\n\n    # make the private loader available in a binding to allow it to be passed on\n    loader_for_function = loader.private_loader\n    here = get_binding(loader_for_function)\n\n    # Avoid reloading the function if already loaded via one of the APIs that trigger 3x function loading\n    # Check if function is already loaded the 3x way (and obviously not the 4x way since we would not be here in the\n    # first place.\n    environment = Puppet.lookup(:current_environment)\n    func_info = Puppet::Parser::Functions.environment_module(environment).get_function_info(typed_name.name.to_sym)\n    if func_info.nil?\n      # This will do the 3x loading and define the \"function_<name>\" and \"real_function_<name>\" methods\n      # in the anonymous module used to hold function definitions.\n      #\n      func_info = eval(ruby_code_string, here, source_ref, 1) # rubocop:disable Security/Eval\n","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb#L7-L43","documentation":"Raised by RubyLegacyFunctionInstantiator when the Ruby source parses cleanly (Ripper.sexp succeeded in assert_code) but the parse tree contains no call to newfunction. Legacy 3x functions must be built with Puppet::Parser::Functions.newfunction; this is a sanity gate before eval'ing the code. If Ripper cannot parse the source at all, the check is skipped and eval fails later instead.","triggerScenarios":"assert_code returns true but assertion_result lacks :found_newfunction: a file in lib/puppet/parser/functions/ (or resolved by the legacy loader) that calls something else -- e.g. 4x-style Puppet::Functions.create_function code in the 3x directory, or plain Ruby with no newfunction call.","commonSituations":"Migrating functions between 3x and 4x APIs and leaving the file in the wrong directory; vendored gem code that does not use the 3x API; helper modules accidentally named like functions.","solutions":["Define the function with Puppet::Parser::Functions.newfunction(:name, :type => :rvalue) { |args| ... }","If the code is 4x API, move it to lib/puppet/functions/<module>/<name>.rb with proper namespacing","Confirm via source_ref which file the legacy loader is actually reading"],"exampleFix":"# before - lib/puppet/parser/functions/double.rb\nPuppet::Functions.create_function(:double) do\n  def double(x); x * 2; end\nend\n\n# after\nmodule Puppet::Parser::Functions\n  newfunction(:double, :type => :rvalue) do |args|\n    args[0] * 2\n  end\nend","handlingStrategy":"validation","validationCode":"sexp = Ripper.sexp(ruby_source)\nif sexp\n  found = sexp.to_s.include?('newfunction')\n  raise ArgumentError, \"#{path} is not a 3x function - no newfunction call\" unless found\nend","typeGuard":null,"tryCatchPattern":"begin\n  fn = loader.load_typed(typed_name)\nrescue ArgumentError => e\n  raise unless e.message.include?(\"no 'newfunction' call\")\n  warn \"not a legacy 3x function: #{e.message}\"\nend","preventionTips":["Keep legacy functions in lib/puppet/parser/functions/ and 4x functions out of it","Use Puppet version 4/5 function API for all new functions","Verify file contents match the directory's expected API before shipping"],"tags":["puppet","legacy-function","ruby","loader","eval"],"backgroundTag":"puppet-legacy-function-load-validation","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}