instructure/canvas-lms · error · ArgumentError

Proc attribute # returned nil when # is required.

Error message

Proc attribute #{name} returned nil when #{name} is required.

What it means

EventStream's attr_config allows attributes whose value is a Proc resolved lazily on read. If such an attribute was declared `required` and its Proc (or callable) returns nil when read, attr_config raises ArgumentError rather than emitting events with a nil required field.

Solutions

  1. Fix the Proc to return a non-nil value (add fallback/default inside the proc)
  2. Remove `required: true` from the attribute if nil is actually acceptable
  3. Guard the event emission site so the proc's input always exists
  4. Log/inspect which attribute is named in the message and audit its data source

Example fix

// before
attr_config :account_id, required: true, type: String
... proc { account&.id }
// after
... proc { account&.id || Canvas::EventStream::NULL_ACCOUNT_ID }
Defensive patterns

Strategy: try-catch

Type guard

def callable_non_nil?(v)
  v.respond_to?(:call) && !v.call.nil?
end

Try / catch

begin
  emit_event(stream)
rescue ArgumentError => e
  raise unless e.message.include?('returned nil when')
  Rails.logger.error("EventStream proc returned nil: #{e.message}")
end

Prevention

When it happens

Trigger: Defining an EventStream::Stream/Index with an attribute configured via a Proc, marking it required, and the Proc returning nil at read time (e.g. the Proc reads session/context data that is absent, or reads a model field that is nil).

Common situations: Livestream attribute procs relying on `current_user`, `request`, or a DB record that is nil in background jobs or after object destruction; misordered initialization so the proc reads before its subject is set.

Related errors


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

Appendix: source

Thrown at gems/event_stream/lib/event_stream/attr_config.rb:62

        type = options[:type]
        typecast = CASTS[type]
      end
      required = !options.key?(:default)
      unless required
        default = options[:default]
        default = typecast.call(name, default) if default && typecast
        attr_config_defaults[name] = default
      end

      define_method(name) do |*args|
        raise ArgumentError if args.length > 1

        if args.empty?
          value = attr_config_values[name]
          if type != Proc && value.respond_to?(:call)
            value = value.call
            if required && value.nil?
              raise ArgumentError, "Proc attribute #{name} returned nil when #{name} is required."
            end

            return typecast.call(name, value) if typecast

            return value
          end
          return value
        end

        value = args.first
        if typecast && !value.respond_to?(:call)
          value = typecast.call(name, value)
        end
        attr_config_values[name] = value
      end
    end
  end

View on GitHub (pinned to 1c9f0bb801)