instructure/canvas-lms · error · ArgumentError

unknown keyword(s): #

Error message

unknown keyword(s): #{unknown_kwargs.map(&:inspect).join(", ")}

What it means

PrefixProxy#fetch accepts only `ttl`, `failsafe_cache`, and a whitelisted `failsafe` keyword. Any other keyword argument is rejected with ArgumentError before any cache or Consul lookup happens. This guards against callers passing stale or typosquatted options (e.g. from an older fetch API) down to fetch_without_request_cache.

Solutions

  1. Remove the unsupported keyword(s) from the fetch call; the message names them exactly
  2. To supply a fallback, fetch and rescue nil-or-handle yourself instead of passing `default:`
  3. Use `failsafe: true` (the only extra allowed kwarg) if you need the on-disk failsafe cache path
  4. Check the canvas dynamic_settings docs/changelog for renamed options

Example fix

// before
settings.fetch('canvas/statsd/namespace', default: 'canvas')
// after
settings.fetch('canvas/statsd/namespace') || 'canvas'
Defensive patterns

Strategy: validation

Validate before calling

def fetch_settings(proxy, key, **opts)
  allowed = %i[ttl failsafe_cache failsafe]
  bad = opts.keys - allowed
  raise ArgumentError, "unsupported fetch opts: #{bad}" if bad.any?
  proxy.fetch(key, **opts)
end

Try / catch

begin
  val = proxy.fetch(key)
rescue ArgumentError => e
  Rails.logger.warn("DynamicSettings fetch kwarg rejected: #{e.message}")
  val = nil
end

Prevention

When it happens

Trigger: Calling DynamicSettings.find(...).fetch('some/key', timeout: 5) or any fetch with an unsupported keyword such as `cache:` or `default:`; also calling through a wrapper that forwards kwargs indiscriminately.

Common situations: Upgrading Canvas: older DynamicSettings fetch options were removed; copy-pasted code from other config loaders passing Ruby Hash#fetch-style options like `default:`; typo in a keyword name.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/24fb675d4695e709. Report an issue: GitHub.

Appendix: source

Thrown at gems/dynamic_settings/lib/dynamic_settings/prefix_proxy.rb:85

    # Fetch the value at the requested key using the prefix passed to the
    # initializer.
    #
    # This method is intended to retreive a single key from the keyspace and
    # will not work for getting multiple values in a hash from the store. If
    # you need to access values nested deeper in the keyspace use #for_prefix
    # to move deeper in the nesting.
    #
    # @param key [String, Symbol] The key to fetch
    # @param ttl [ActiveSupport::Duration] The TTL for the value in the cache,
    #   defaults to value supplied to the constructor.
    # @param failsafe_cache [false, PathInfo] Location on disk to store a
    #   failsafe cached for this value, in case Consul is down on a future boot.
    #   Should be used sparingly, since it will load a file off disk.
    # @return [String]
    # @return [nil] When no value was found
    def fetch(key, ttl: @default_ttl, failsafe_cache: false, **kwargs)
      unknown_kwargs = kwargs.keys - [:failsafe]
      raise ArgumentError, "unknown keyword(s): #{unknown_kwargs.map(&:inspect).join(", ")}" unless unknown_kwargs.empty?

      # Within a given request, no reason to talk to redis/consul multiple times for the same key in the same tree
      # The TTL is only relevant for the underlying cache-within a request we don't exceed the ttl boundary
      DynamicSettings.request_cache.cache(CACHE_KEY_PREFIX + full_key(key)) do
        fetch_without_request_cache(key, ttl:, failsafe_cache:, **kwargs)
      end
    end
    alias_method :[], :fetch

    # Extend the prefix from this instance returning a new one.
    #
    # @param prefix_extension [String]
    # @param default_ttl [ActiveSupport::Duration] The default TTL to use when
    #  fetching keys from the extended keyspace, defaults to the same value as
    #  the receiver
    # @return [ProxyPrefix]
    def for_prefix(prefix_extension, default_ttl: @default_ttl)
      self.class.new(

View on GitHub (pinned to 1c9f0bb801)