Designing and operating feature flags: flag types, rollout strategies, kill switches, and cleanup discipline for agents shipping code changes. Triggers on: roll out a feature gradually, add a kill switch, feature flag design, darken-launch, remove old flags.
./install.sh feature-flagsname: "feature_flags" description: "Designing and operating feature flags: flag types, rollout strategies, kill switches, and cleanup discipline for agents shipping code changes. Triggers on: roll out a feature gradually, add a kill switch, feature flag design, darken-launch, remove old flags." metadata: { "includeInPrompt": true }
Feature flags decouple deployment from release: ship code to trunk behind a flag, then expose it to users on your own schedule. Use them to roll out gradually, run experiments, and kill a bad change in seconds without a redeploy. The discipline that makes them work is cleanup — an undeleted flag is permanent conditional logic in your codebase, i.e. tech debt you opted into.
team.feature.state (e.g. checkout.applepay.v2). Record owner + expiry date at creation in your flag registry — a registry row without both is rejected.Given a change to ship safely, produce:
team.feature.state format, plus owner and expiry date.Flag types (one paragraph each):
Rollout strategies. Boolean on/off for kill switches and dark launches. Percentage rollout with consistent hashing on a stable user key — stable assignment matters because a user flipping between variants mid-session sees inconsistent behavior and corrupts experiment data. Targeted rollout by user IDs, cohorts, or geography for staged exposure. Ring/canary deployments for infra changes (one region, then all).
Evaluation sketch (verified — see report). Context-based rules evaluated top-down: kill switch first, then explicit targets, then percentage bucket, then default. Evaluation failures return the safe default.
python
import hashlib
def _bucket(user_key: str, flag: str) -> float:
h = hashlib.sha256(f"{flag}:{user_key}".encode()).hexdigest()
return int(h[:8], 16) / 0xFFFFFFFF # stable 0..1
def get_flag(user_key, flag_name, rules, default=False):
try:
r = rules.get(flag_name) or {}
if r.get("kill"): return False # kill switch wins
if user_key in r.get("users", set()): return True # targeted
return _bucket(user_key, flag_name) * 100 < r.get("pct", 0)
except Exception:
return default # eval failure -> fail-closed to safe value
Tooling landscape. OpenFeature: open standard / vendor-neutral SDK — prefer when you want to avoid lock-in or swap providers. Managed (LaunchDarkly): prefer when you want dashboards, targeting UI, and audit trails without operating infra. Self-hosted / open-source (Unleash, Flipt, Flagsmith): prefer when data must stay in your infrastructure or cost matters. Zero-dependency option for small projects — a config file:
yaml
flags:
new-checkout: { users: [u_alice], pct: 50, default: false }
Lifecycle / cleanup discipline. This is the #1 operational failure mode, so it gets teeth: every flag is created with an owner and an expiry date, recorded in the registry. Cleanup rule — remove the flag within 2 sprints of reaching 100% rollout (release/experiment/migration types); permission flags get a quarterly review instead. Run a stale-flag audit on a fixed cadence (monthly for active teams): any flag past expiry without a renewed date gets a removal ticket assigned to its owner. Prefer deleting the old path over leaving "just in case" branches — the flag did its job; keeping it is the debt.
Anti-patterns. Flags that are never removed (the default outcome without the cleanup rule above). Flag-dependent flags — nesting one flag's evaluation inside another's branch; flatten to independent flags or a single rule set. Using flags as general config for non-rollout settings (timeouts, URLs, credentials) — that's what config management is for; flags gate behavior changes. Percentage rollout keyed on unstable identifiers (session ID, request ID, IP) — assignment must survive across requests, so key on user ID or another stable key.
publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e
No ratings yet — be the first to rate it.