fetlife/rollout
Feature flippers.
Healthy across all four use cases
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 6mo ago
- ✓17 active contributors
- ✓MIT licensed
Show 4 more →Show less
- ✓CI configured
- ✓Tests present
- ⚠Slowing — last commit 6mo ago
- ⚠Concentrated ownership — top contributor handles 71% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/fetlife/rollout)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/fetlife/rollout on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: fetlife/rollout
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/fetlife/rollout shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- Last commit 6mo ago
- 17 active contributors
- MIT licensed
- CI configured
- Tests present
- ⚠ Slowing — last commit 6mo ago
- ⚠ Concentrated ownership — top contributor handles 71% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live fetlife/rollout
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/fetlife/rollout.
What it runs against: a local clone of fetlife/rollout — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in fetlife/rollout | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 202 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of fetlife/rollout. If you don't
# have one yet, run these first:
#
# git clone https://github.com/fetlife/rollout.git
# cd rollout
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of fetlife/rollout and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "fetlife/rollout(\\.git)?\\b" \\
&& ok "origin remote is fetlife/rollout" \\
|| miss "origin remote is not fetlife/rollout (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "lib/rollout.rb" \\
&& ok "lib/rollout.rb" \\
|| miss "missing critical file: lib/rollout.rb"
test -f "lib/rollout/feature.rb" \\
&& ok "lib/rollout/feature.rb" \\
|| miss "missing critical file: lib/rollout/feature.rb"
test -f "lib/rollout/logging.rb" \\
&& ok "lib/rollout/logging.rb" \\
|| miss "missing critical file: lib/rollout/logging.rb"
test -f "gemspec" \\
&& ok "gemspec" \\
|| miss "missing critical file: gemspec"
test -f "spec/rollout_spec.rb" \\
&& ok "spec/rollout_spec.rb" \\
|| miss "missing critical file: spec/rollout_spec.rb"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 202 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~172d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/fetlife/rollout"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Rollout is a Redis-backed feature flag library for Ruby that enables fast, granular control over feature availability. It allows you to activate features for specific users, user percentages, or groups without code deploys, using mechanisms like CRC32-based consistent hashing for user percentage rollouts and custom group predicates for behavioral targeting. Monolithic gem structure: lib/rollout.rb is the main entry point, with lib/rollout/feature.rb encapsulating individual feature state and lib/rollout/logging.rb handling observability. Tests mirror the structure under spec/ (spec/rollout_spec.rb for core, spec/rollout/feature_spec.rb and spec/rollout/logging_spec.rb for modules). All state lives in Redis, not in-process.
👥Who it's for
Ruby backend engineers at FetLife and other platforms who need to safely roll out new features, run A/B tests, and manage beta access without redeploying. Specifically developers building Rails apps or other Ruby services that require feature flags tied to user identity.
🌱Maturity & risk
Production-ready and actively maintained. The project has CI/CD via GitHub Actions (.github/workflows/test.yml and release.yml), RSpec test coverage (spec/ directory), RuboCop linting (.rubocop.yml), and appears to be a stable, well-established gem (indicated by the badge URLs and consistent test infrastructure). Recent GitHub Actions setup suggests ongoing maintenance.
Low risk for stable use cases. The single-language focus (Ruby) and tight Redis dependency mean it's well-suited to Ruby shops but not cross-platform. No visible dependency explosion (gemspec exists but dependencies likely minimal for a feature flag library). Main risk is single-maintainer bias on FetLife's GitHub, but the mature test suite and simple scope minimize this. No breaking changes are apparent from the file structure.
Active areas of work
The repository appears actively maintained with GitHub Actions CI/CD workflows configured (test.yml runs on commits, release.yml handles publishing). No specific PR or milestone data is visible in the provided structure, but the presence of modern GitHub Actions (vs. the legacy .travis.yml) suggests recent activity. The logging module (lib/rollout/logging.rb) indicates ongoing enhancement for observability.
🚀Get running
Clone the repo, install Ruby (see .ruby-version), bundle dependencies, and run tests:
git clone https://github.com/fetlife/rollout.git
cd rollout
bundle install
bundle exec rspec
Daily commands:
No server to run. This is a library gem. After bundle install, use it in your Ruby app by requiring it and initializing with a Redis connection:
require 'rollout'
$rollout = Rollout.new($redis)
$rollout.active?(:feature_name, user)
Run tests with bundle exec rspec.
🗺️Map of the codebase
lib/rollout.rb— Main entry point and core Rollout class; implements feature flag logic, Redis operations, and public API for checking feature activationlib/rollout/feature.rb— Feature abstraction layer; encapsulates individual feature state, percentages, users, and groups—essential to understand the data modellib/rollout/logging.rb— Logging integration; handles debug output for feature flag checks and state changes—critical for troubleshooting in productiongemspec— Gem metadata and dependency declarations; defines public API contract and external requirementsspec/rollout_spec.rb— Primary integration tests; demonstrates intended usage patterns and expected behavior for the core API
🧩Components & responsibilities
- Rollout (main class) (Ruby, Redis) — Orchestrates feature flag operations; manages Redis connection, delegates to Feature for state logic, exposes public API
- Failure mode: Redis unavailable → all active?() calls return false (features disabled); network latency adds ~2–50ms per check
- Feature (model) (Ruby) — Encapsulates single feature state; computes activation for a user by checking whitelist, groups, and percentage
- Failure mode: Corrupt JSON in Redis → Feature parsing fails; missing user.id → percentage check fails silently
- Redis store (Redis) — Persists feature state (users, groups, percentage, metadata) as JSON; shared across all application instances
- Failure mode: Data loss → all feature state lost; slow response → application checks block; memory full → eviction may delete flags
🔀Data flow
Application→Rollout— active?(feature_name, user) query with user objectRollout→Redis— GET rollout:feature_name to fetch serialized feature stateRedis→Feature (model)— JSON parsed into Feature instance with users[], groups[], percentage, metadataFeature→User— Calls user.in_group?(group_name) to check group membershipFeature→Rollout— Returns boolean activation decision to callerApplication→Rollout— enable_user(), enable_group(), enable_percentage() mutationsRollout→Redis— SET/DEL rollout:feature_name with updated JSON state
🛠️How to make changes
Add a new feature flag
- Initialize Rollout with a Redis connection in your application setup (e.g., Rails initializer) (
lib/rollout.rb) - Call $rollout.activate_feature(:feature_name) to create the flag, or use $rollout.set_feature_data(:feature_name, {metadata}) to add metadata (
lib/rollout.rb) - In code, check $rollout.active?(:feature_name, user_object) or $rollout.active?(:feature_name) for global checks (
lib/rollout.rb)
Enable a feature for a percentage of users
- Call $rollout.enable_percentage(:feature_name, 25) to activate for 25% of unique user IDs (deterministic hash-based) (
lib/rollout/feature.rb) - The Feature class uses a consistent hash of user.id % 100 to determine eligibility—no per-user state required (
lib/rollout/feature.rb)
Enable a feature for specific users or groups
- Call $rollout.enable_user(:feature_name, user_object) to add an individual user to the whitelist (
lib/rollout.rb) - Call $rollout.enable_group(:feature_name, :admin_group) to enable for all users in a named group (
lib/rollout.rb) - Define group membership by implementing a method like user.in_group?(group_name) on your user model (
lib/rollout/feature.rb)
Debug feature flag activation
- Enable logging by requiring and mixing in the Logging module in Rollout (
lib/rollout/logging.rb) - Call $rollout.get_feature(:feature_name) to inspect the Feature object state (users, groups, percentage) (
lib/rollout.rb)
🔧Why these technologies
- Redis — Provides fast, atomic operations for feature state storage and retrieval; enables horizontal scaling across application instances with shared state
- Ruby Gems (Gemfile) — Standard Ruby packaging and dependency management; allows integration into Rails and other Ruby applications with minimal friction
- RSpec — Ruby standard testing framework; enables comprehensive integration and unit test coverage for feature flag behavior
⚖️Trade-offs already made
-
Percentage-based rollout uses deterministic user.id hashing instead of per-user state
- Why: Avoids storing millions of individual user records in Redis; scales linearly with feature count, not user count
- Consequence: Same user always gets same flag result; no A/B test group switching mid-rollout; percentage must be reconfigured to change outcomes
-
Features stored as serialized JSON blobs in Redis keys, not normalized tables
- Why: Simpler operational model; single Redis GET returns complete feature state
- Consequence: Harder to query across features (e.g., 'which features are enabled for user X'); all users see identical feature config
-
No built-in persistence layer abstraction—Redis client passed directly to Rollout
- Why: Reduced abstraction overhead; single Redis instance or cluster per application
- Consequence: Tight coupling to Redis; switching backends requires rewriting core; no fallback store if Redis unavailable
🚫Non-goals (don't propose these)
- Does not provide UI/dashboard for feature management—programmatic API only
- Does not support time-based scheduling or gradual rollouts—activation is immediate or percentage-based
- Does not offer audit logging or change history—only current feature state stored
- Does not provide high-availability automatic failover—requires external Redis HA setup
- Does not implement flag caching in application memory—every check hits Redis
⚠️Anti-patterns to avoid
- Direct user object type checking in activation logic (Low) —
lib/rollout/feature.rb: Feature class checks user.respond_to?(:id) instead of using duck typing or explicit interface; fragile if user model structure changes - No nil checks on group method calls (Medium) —
lib/rollout/feature.rb: Calls user.in_group?(group_name) without verifying method exists; will raise NoMethodError if user class doesn't implement groups - Global Redis state without transaction safety (Medium) —
lib/rollout.rb: Multiple operations (read-check-write) on Redis keys are not atomic; race condition possible if feature state changes between checks
🔥Performance hotspots
lib/rollout.rb active?() method(I/O latency) — Every feature check requires one Redis GET roundtrip (~2–50ms); no in-app caching; high cardinality user checks can saturate Redislib/rollout/feature.rb in_group?() calls(Logic inefficiency) — User group membership check is N+1 prone if calling in_group? for multiple groups; no batching or group pre-computationlib/rollout.rb get_feature() method(undefined) — Deserializes entire feature JSON even if only checking percentage; no field-level querying in
🪤Traps & gotchas
Redis connectivity is implicit — if Redis is not running or REDIS_URL env var is not set correctly, initialization will fail silently or with unclear errors. The CRC32-based user percentage algorithm (mentioned in README) relies on Zlib::crc32, which must be available. Group definitions are not persisted and must be re-registered on every app restart, leading to potential bugs if groups drift. Activating a feature for 100% also globally enables it, which can be surprising when calling active? without a user argument.
🏗️Architecture
💡Concepts to learn
- CRC32 Consistent Hashing for User Percentages — Rollout uses Zlib::crc32(user.id) % 2**32 to deterministically assign users to percentage buckets, ensuring the same user always sees the same feature state across requests and that rollout percentages are sticky (users 0-19% stay in as percentage increases)
- Feature Flag Activation Strategies (Multi-strategy Pattern) — Rollout combines multiple orthogonal activation modes (groups, specific users, percentages, global) that can all be active simultaneously for a single feature, requiring understanding of precedence and composition
- Redis as Feature Flag State Store — All feature activation data (groups, users, percentages) lives in Redis, not in code or config files, enabling real-time flag changes without redeployment but requiring Redis availability
- Predicate-based Group Membership (Ruby Blocks) — Custom groups are defined via closures (define_group { |user| user.caretaker? }) evaluated at runtime, allowing behavior-driven feature access without pre-computing membership lists
- Global vs. User-Scoped Feature State — Rollout distinguishes between 'is feature on for anyone?' (global, no user needed) and 'is feature on for this specific user?' (user-scoped); 100% activations blur this boundary
- Gem Structure & Ruby Packaging — Understanding lib/, spec/, gemspec, and Bundler conventions is essential to navigate, test, and extend this library
🔗Related repos
jnunemaker/flipper— Direct competitor in Ruby feature flag space; more mature with UI dashboard, but Rollout is simpler and Redis-nativeLaunchDarkly/ruby-server-sdk— Enterprise feature flag solution; Rollout is a lightweight alternative for teams wanting minimal external dependenciesredis/redis-rb— The Redis client library that Rollout depends on; understanding this is essential for debugging persistence issuesfetlife/rollout-ui— Likely companion dashboard UI for managing Rollout feature flags visually (if it exists in FetLife's GitHub)rspec/rspec— The testing framework used throughout this repo; familiarity is required for running and writing tests
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add integration tests for Redis persistence in rollout_spec.rb
The spec/rollout_spec.rb file likely tests basic feature flag behavior, but there are no explicit integration tests verifying that feature state correctly persists to and loads from Redis. This is critical for a Redis-backed feature flag system. New contributors can add tests that verify data survives across Rollout instance recreations and that concurrent access patterns work correctly.
- [ ] Review spec/rollout_spec.rb to identify gaps in Redis persistence testing
- [ ] Add tests that verify feature flags persist after creating a new Rollout instance with the same Redis connection
- [ ] Add tests for edge cases like expired keys, missing keys, and concurrent modifications
- [ ] Run
bundle exec rspec spec/rollout_spec.rbto verify tests pass
Add comprehensive logging tests in spec/rollout/logging_spec.rb
lib/rollout/logging.rb exists but spec/rollout/logging_spec.rb likely has minimal coverage. Feature flag systems need robust logging for debugging and auditing. A contributor can expand the logging spec to verify all logging scenarios (feature activation/deactivation, user inclusion/exclusion, errors) work correctly and log appropriate context.
- [ ] Review lib/rollout/logging.rb to understand all logging touchpoints
- [ ] Expand spec/rollout/logging_spec.rb with tests for each logging scenario (activation, user changes, errors)
- [ ] Verify logs include contextual information (feature name, user ID, timestamp)
- [ ] Run
bundle exec rspec spec/rollout/logging_spec.rbto confirm coverage
Add GitHub Actions workflow for Gem release automation in .github/workflows/release.yml
The release.yml file exists but may lack full automation for publishing to RubyGems and GitHub releases. A contributor can enhance the release workflow to automatically build, test, and publish the gem when a version tag is pushed, reducing manual release work and preventing human error.
- [ ] Review .github/workflows/release.yml to identify missing release steps
- [ ] Add steps to build the gem using
gem build rollout.gemspec - [ ] Add authentication and publication to RubyGems API using GitHub secrets (RUBYGEMS_API_KEY)
- [ ] Add automatic GitHub Release creation with changelog/version info
- [ ] Test the workflow by creating a test tag and verifying dry-run succeeds
🌿Good first issues
- Write integration tests in spec/ that verify the full workflow of activating a feature for a user percentage, then checking active? for different users to ensure the CRC32 algorithm produces the expected consistent distribution.
- Add documentation / examples for using Rollout with Rails initializers (e.g., config/initializers/rollout.rb) since the current README shows global initialization but not Rails-specific patterns.
- Extend the logging module (lib/rollout/logging.rb) with concrete examples of logging when active? returns true/false per feature, and add tests for those log outputs in spec/rollout/logging_spec.rb.
⭐Top contributors
Click to expand
Top contributors
- @reneklacan — 71 commits
- @michal-granec — 3 commits
- @Pezmc — 3 commits
- @p — 3 commits
- @kerrizor — 3 commits
📝Recent commits
Click to expand
Recent commits
5ba5fc6— Merge pull request #177 from fetlife/move-from-circleci-to-github-completely (michal-granec)29da5d5— Remove CircleCI config (michal-granec)6772c88— Add ruby version matrix (michal-granec)9c688b7— Release v2.6.2 (Pezmc)5cc1364— Enable redis and pass API key via credentials file (Pezmc)4699d17— Add support for automated-releases (Pezmc)a88d9b2— Bump version 2.6.1 (reneklacan)8d38af6— Add observer to deps to resolve Ruby 3.3 warning (reneklacan)1c59de2— Add rake to dev deps (reneklacan)3ad09c5— Merge pull request #170 from fetlife/v2.6 (reneklacan)
🔒Security observations
The rollout gem appears to be a well-maintained feature flagging library with no critical vulnerabilities immediately evident. Primary concerns are around secure Redis configuration (authentication, TLS) which are deployment-level rather than code-level issues. The codebase itself seems straightforward with limited attack surface. Key recommendations: enforce secure Redis connections in documentation, maintain up-to-date dependencies, and ensure comprehensive input validation. Missing dependency audit data prevents complete assessment.
- Medium · Redis Connection Without Authentication Configuration —
README.md, lib/rollout.rb initialization. The README shows initialization of Redis without explicit authentication parameters. While Redis.new() can use REDIS_URL env var, there's no clear guidance on enforcing secure connections (TLS/SSL) or authentication, which could lead to unauthorized access if deployed in insecure environments. Fix: Document and enforce Redis connection security best practices: use REDIS_URL with redis:// or rediss:// schemes, require password authentication, and enable TLS/SSL in production environments. - Low · Missing Dependency Lock File Content Review —
Gemfile, Gemfile.lock, rollout.gemspec. The dependency file content was not provided for analysis. Without visibility into the Gemfile and Gemfile.lock, potential vulnerable gems cannot be assessed. Fix: Regularly audit dependencies using 'bundle audit' or 'bundler-audit' and keep all gems updated to latest patched versions. Include dependency vulnerability scanning in CI/CD pipeline. - Low · No Input Validation Patterns Visible —
lib/rollout.rb, lib/rollout/feature.rb. Based on the file structure, there's limited visibility into input validation practices within the feature flag implementation. Feature flag names and data could potentially be exploited if not properly validated. Fix: Implement strict input validation for feature flag names, user identifiers, and metadata. Sanitize all user-provided inputs before processing or storing in Redis.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.