flippercloud/flipper · error · Flipper::DuplicateGroup
Group #{name.inspect} has already been registered
Error message
Group #{name.inspect} has already been registered What it means
Flipper keeps a process-global registry of groups (Flipper.groups_registry). Flipper.register(name, &block) inserts the group into that registry, and a second registration of the same name raises Flipper::DuplicateGroup instead of silently replacing the first block. This guards against two different match blocks both claiming one group name, which would make actor matching ambiguous. The registry only resets via Flipper.unregister_groups or process restart.
Source
Thrown at lib/flipper.rb:138
#
# name - The Symbol name of the group.
# block - The block that should be used to determine if the group matches a
# given actor.
#
# Examples
#
# Flipper.register(:admins) { |actor|
# actor.respond_to?(:admin?) && actor.admin?
# }
#
# Returns a Flipper::Group.
# Raises Flipper::DuplicateGroup if the group is already registered.
def register(name, &block)
group = Types::Group.new(name, &block)
groups_registry.add(group.name, group)
group
rescue Registry::DuplicateKey
raise DuplicateGroup, "Group #{name.inspect} has already been registered"
end
# Public: Returns a Set of registered Types::Group instances.
def groups
groups_registry.values.to_set
end
# Public: Returns a Set of symbols where each symbol is a registered
# group name. If you just want the names, this is more efficient than doing
# `Flipper.groups.map(&:name)`.
def group_names
groups_registry.keys.to_set
end
# Public: Clears the group registry.
#
# Returns nothing.
def unregister_groupsView on GitHub (pinned to 1f86de3ec9)
Solutions
- Register each group only once, and place registration in config/initializers/flipper.rb (runs once per process) rather than reloadable app code.
- If registration may run repeatedly (to_prepare, tests), guard it: Flipper.register(:admins) { ... } unless Flipper.group_exists?(:admins).
- In test suites, add Flipper.unregister_groups to a before/after hook so each example starts clean.
- If you genuinely must redefine a group at runtime, call Flipper.unregister_groups first, then re-register everything.
Example fix
// before (app/flipper_groups.rb — Zeitwerk reloads this in dev, second load raises)
Flipper.register(:admins) { |actor| actor.respond_to?(:admin?) && actor.admin? }
# after (config/initializers/flipper.rb — idempotent, runs once per process)
Rails.application.config.to_prepare do
Flipper.register(:admins) { |actor| actor.respond_to?(:admin?) && actor.admin? } unless Flipper.group_exists?(:admins)
end Defensive patterns
Strategy: validation
Validate before calling
Flipper.register(:admins) { |actor| actor.admin? } unless Flipper.group_exists?(:admins) Try / catch
begin Flipper.register(:admins, &block) rescue Flipper::DuplicateGroup # existing registration is authoritative; only safe if the block is identical raise if ENV["RAILS_ENV"] == "production" end
Prevention
- Register groups in config/initializers (runs once per process), never in reloadable app code.
- Make registration idempotent with Flipper.group_exists?(name) guards.
- Call Flipper.unregister_groups in test before/after hooks.
- Centralize all Flipper.register calls in one file so duplicates are obvious in review.
When it happens
Trigger: Calling Flipper.register(:admins) { ... } twice with the same Symbol in one process; calling Flipper.setup/initialization code that registers groups more than once; registering groups in a file that gets loaded twice (require + Zeitwerk autoload, or initializer re-run by Spring/rake tasks that reload initializers).
Common situations: Rails development reloading: groups registered in app/ code that Zeitwerk reloads, or in config.to_prepare blocks that fire on every code reload; registering the same group in both an initializer and a monkey patch/decorator; RSpec suites that call Flipper.register in a helper without unregistering between examples; engines/gems that each register the same group name.
Related errors
- Your database needs to be migrated to use the latest Flipper
- Actor limit of #{@limit} exceeded for feature #{feature.key}
- Failed with status: #{response.code}
- #{data_type} is not supported by this adapter
- #{gate} is not supported by this adapter yet
AI-assisted analysis of flippercloud/flipper@1f86de3ec9 (2026-08-23).
Data as JSON: /api/errors/87601bc8d9381fe2.
Report an issue: GitHub.