discourse/discourse · error · PresenceChannel::InvalidConfig

Expected PresenceChannel::Config or nil. Got a #

Error message

Expected PresenceChannel::Config or nil. Got a #{result.class.name}

What it means

Raised in PresenceChannel#fetch_config as PresenceChannel::InvalidMessage when a registered config block returns a value that is neither a PresenceChannel::Config nor nil. Only those two shapes are accepted (nil is cached as the NOT_FOUND sentinel); anything else means a misbehaving channel configuration block. The at-fault input is the return value of the block registered for the channel's prefix.

Solutions

  1. Fix the config block for that prefix to return a PresenceChannel::Config for existing channels or nil for non-existent ones
  2. Check for blocks that accidentally return a string/hash or raise-dependent truthy value
  3. Test the block directly with representative channel names to confirm return types
Defensive patterns

Strategy: type-guard

When it happens

Trigger: Thrown at lib/presence_channel.rb:323 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of discourse/discourse@1b2d7253e8 (2026-08-26). Data as JSON: /api/errors/e3fef4a4919aa44a. Report an issue: GitHub.

Appendix: source

Thrown at lib/presence_channel.rb:323

    elsif cached_config
      Config.from_json(cached_config)
    else
      prefix = name[%r{/([a-zA-Z0-9_-]+)/.*}, 1]
      raise PresenceChannel::NotFound if prefix.nil?

      config_block = @@configuration_blocks[prefix]
      config_block ||=
        DiscoursePluginRegistry.presence_channel_prefixes.find { |t| t[0] == prefix }&.[](1)
      raise PresenceChannel::NotFound if config_block.nil?

      result = config_block.call(name)
      to_cache =
        if result.is_a? Config
          result.to_json
        elsif result.nil?
          Config::NOT_FOUND
        else
          raise InvalidConfig.new "Expected PresenceChannel::Config or nil. Got a #{result.class.name}"
        end

      DiscourseRedis.ignore_readonly do
        PresenceChannel.redis.set(redis_key_config, to_cache, ex: CONFIG_CACHE_SECONDS)
      end

      raise PresenceChannel::NotFound if result.nil?
      result
    end
  end

  def publish_message(entering_user_ids: nil, leaving_user_ids: nil)
    message = {}
    if config.count_only
      message["count_delta"] = entering_user_ids&.count || 0
      message["count_delta"] -= leaving_user_ids&.count || 0
      return if message["count_delta"] == 0
    else

View on GitHub (pinned to 1b2d7253e8)