The feature flag decision record: why the flag lifecycle you configured determines your technical debt accumulation surface and your rollout correctness failure mode
Feature flags are introduced to solve a deployment problem — the ability to ship code before enabling it, to enable it gradually, and to disable it instantly if something goes wrong. The founding flag convention is set by the first engineer who adds a flag, and that convention — the naming pattern, the ownership model, the cleanup expectation, the targeting attribute contract — is inherited by every subsequent flag without a decision record that makes it explicit. Three failure patterns develop from the implicit convention: the team that accumulated 240 flags over two years with no ownership documentation and accidentally disabled the wrong billing path — the investigation took a week because no one could enumerate what each flag controlled; the team whose premium-feature flag silently turned off for all new enterprise customers when the billing system renamed the plan attribute the flag evaluated against, and nobody noticed for three weeks because the flag dashboard showed the flag as enabled with a valid targeting rule; and the team whose database migration flags were never removed after the migration completed, leaving an old query path in the codebase that received no security patches for eight months because it was assumed to be dead — an assumption a security audit disproved.
A 29-person SaaS company that built project portfolio tooling for software engineering teams had introduced feature flags in its second year, when the engineering team began deploying code to production more frequently and needed a way to decouple deployment from enablement. The first flag had been added by a senior engineer managing a high-risk refactor of the billing calculation engine. She had wrapped the new billing code path in a flag named new_billing_engine, deployed it disabled, tested it in staging, and then slowly ramped the flag from 0% to 100% over two weeks while monitoring error rates. The rollout had gone cleanly. The flag had been set to 100% and left in the flag platform because removing the conditional from the code required a follow-up pull request that was always slightly lower priority than whatever the next sprint contained.
Over the following two years, the engineering team adopted feature flags as the standard deployment mechanism for anything with production risk. Each new feature, each infrastructure migration, each A/B experiment had a flag. Engineers named their flags according to their own conventions — some used snake_case noun phrases, some used kebab-case verb phrases, some prefixed with the feature area, some did not. Ownership was implicit: whoever created the flag owned it. When engineers left the company or changed teams, their flags remained in the platform with no updated owner. By the end of month thirty, the LaunchDarkly project contained 240 flags. Of those, the engineering team's best estimate was that roughly 30 flags were associated with active rollouts or experiments. The remaining 210 were in an always-on or always-off state with no documentation of why.
A junior engineer joining the team in month thirty-one was assigned a task to clean up the billing area of the codebase. He encountered a cluster of flags with names like new_billing_engine, billing_v2_enabled, billing_recalculation_path, and legacy_billing_fallback. The names implied they were migration artifacts. He assumed they were all set to their final values — that whatever the new billing path was, it was already on, and whatever the legacy path was, it was already off. He drafted a pull request removing the flag conditionals and keeping the code paths that corresponded to what he read as the "new" or "enabled" state for each flag. For three of the four flags, this was correct. For legacy_billing_fallback, the name was misleading — the flag had been added not to enable a legacy fallback but to disable a new calculation feature for a specific cohort of grandfathered customers who had signed contracts with the original pricing model. The flag was set to "on" for those customers not as a legacy path but as their permanent billing configuration. Removing the conditional and keeping the "disabled" code path meant those customers were billed under the new calculation model. Invoices for forty-three customers were incorrect in the next billing cycle.
The investigation required a week — not because the code change was hard to identify, but because establishing what the legacy_billing_fallback flag had actually been doing required reconstructing the context from Slack messages, commit history, and one engineer who had been on the team at the time the flag was added and remembered the grandfathered customer situation. The LaunchDarkly entry for the flag had a description field that had been left blank. The pull request that introduced the flag had a one-line commit message: "add billing fallback flag." The documentation that would have prevented the incident — the flag's purpose, the customer segment it targeted, the business reason the always-on state was permanent rather than a completed rollout — had never been written because at the time the flag was added, the context was shared knowledge on a team of twelve people. It was not shared knowledge on a team of twenty-nine people two years later.
A 36-person SaaS company that built customer feedback management tooling for product teams had been using LaunchDarkly for eighteen months when the failure occurred. The company had a mature flag discipline relative to its size: a naming convention for flags, a Slack channel where flag changes were announced, a general awareness that flags should be cleaned up after rollouts completed. What the team had not documented was the targeting attribute contract — the specific attributes that flag evaluation rules relied on, the systems that produced those attributes, and the protocol for keeping flag rules synchronized when the source systems changed.
Twelve months earlier, a product manager had created a flag called advanced-sentiment-analysis to gate a premium AI feature to the company's enterprise customers. She had configured the targeting rule to serve the enabled variation to any user whose account carried the attribute plan with the value enterprise. The flag had worked correctly for eleven months. Every enterprise customer could access the sentiment analysis feature. Every free and professional customer could not. The targeting rule was simple and readable in the LaunchDarkly UI: "If account.plan is 'enterprise', serve true."
In month twelve, the billing team completed a migration of the company's Stripe subscription configuration. The migration had been planned for three months to resolve an accounting problem: the company could not distinguish in its billing reports between enterprise customers on annual contracts and enterprise customers on monthly contracts. The solution was to rename the Stripe product price IDs and update the account sync that wrote plan attributes to the identity database. After the migration, accounts that had carried plan: "enterprise" were updated to carry either plan: "enterprise_annual" or plan: "enterprise_monthly" depending on their billing cycle. The migration ran cleanly. The billing reports worked correctly. No one had checked whether any feature flag targeting rules evaluated the plan attribute.
The day after the billing migration, the advanced-sentiment-analysis flag began serving the disabled variation to every enterprise customer whose account had been updated to the new plan values. Existing sessions where the user had already loaded the feature continued working until their session expired. New logins, new accounts, and new sessions for existing users all failed to match the targeting rule. The LaunchDarkly dashboard showed the flag as enabled with an active targeting rule. The rule evaluated without error. The flag served the disabled variation because no account in the system carried plan: "enterprise" any longer — every enterprise account had been updated to one of the two new values. The flag was technically functioning exactly as specified.
Three weeks elapsed before the failure was diagnosed. During that period, the company received fourteen support tickets from enterprise customers reporting that the sentiment analysis feature was unavailable. Support routed the tickets to the product team. The product team verified their own access and found the feature working — because the product team's own accounts had been updated to enterprise_annual but their sessions had been established before the billing migration and had not expired. When their sessions did expire and they logged in fresh, the feature was gone for them too. The diagnosis came when an engineer querying the analytics database for a different reason noticed a sharp drop in sentiment_analysis_viewed events on the day of the billing migration. She pulled the flag evaluation logs from LaunchDarkly and found that the targeting rule had been returning false for every account since the migration date. The fix was a thirty-second targeting rule update adding enterprise_annual and enterprise_monthly as matching values. The three weeks of degraded service for enterprise customers and the support ticket backlog had been the cost of the missing attribute contract.
A 43-person SaaS company that built data pipeline tooling for analytics teams had undertaken a major database migration in its third year — moving its primary query execution layer from a custom ORM to a purpose-built query engine that offered better performance and a cleaner extension model. The migration had been managed through feature flags from the start: a set of flags controlled which query path was active for each query type, allowing the team to migrate one query type at a time, test thoroughly, and roll back instantly if something went wrong. The migration had proceeded over eight months and had been pronounced complete when the last query type was moved to the new path and all flags were set to their final always-on states.
The engineering team had discussed cleaning up the flags after the migration completed. The consensus had been to leave the flags in place for sixty days as a safety net — if a regression appeared, the rollback path was the flags. After sixty days, the cleanup had been added to the backlog. It had remained in the backlog for the following eighteen months while higher-priority work consumed each sprint. The flags stayed in the LaunchDarkly dashboard at their always-on values. The old query path code stayed in the codebase behind the always-on conditionals. The team's implicit model was that the old code was dead — the flags were always on, the new path was always executed, the old path was unreachable in practice regardless of whether it remained in the repository.
Eight months after the migration was complete, the security team patched a SQL injection vulnerability in the new query path. A user-supplied filter parameter was being interpolated directly into a query string in two places. The patches were applied to the new query path files. The old query path files contained the same interpolation pattern but were not reviewed in the security patch because the old path was assumed to be dead. No one had documented whether the old path was actually unreachable — whether the flags could evaluate to anything other than always-on under any condition — because the flags had been in the always-on state for eight months and the assumption of permanent always-on had never been formalized as a documented decision.
Eighteen months after the migration completed, a security audit conducted by a prospective enterprise customer's security team identified the vulnerability. The auditor requested the codebase for review, ran a static analysis scan, and flagged the interpolation pattern in the old query path as a critical finding. The engineering team's initial response was that the code path was dead. The auditor asked for documentation confirming that the flags controlling the old path could not evaluate to false under any condition. That documentation did not exist. The team ran an analysis of the flag evaluation history in LaunchDarkly's audit log, the flag configuration, and the SDK initialization code and concluded — correctly — that the old path was unreachable in production because the flags were hardcoded to always-on at the SDK initialization level. But the analysis required three days of engineer time and produced a written report that was attached to the audit finding as compensating evidence, not as documentation that had existed before the finding.
The audit also surfaced a secondary finding: of the company's 340 flags in the LaunchDarkly project, 231 had been in always-on or always-off state for more than twelve months. Of those 231, 67 had no description, 44 had owners who had left the company, and 12 were in always-off state with active code paths that had never been tested in the production environment because the flags had never been enabled there. The flag inventory had grown to a size where its operational state — which flags were temporary, which were permanent, which were forgotten — was not recoverable from the platform metadata without a significant manual audit. The original flag convention, implicit and undocumented, had produced an inventory that reflected the company's rollout history rather than its current operational configuration.
Structural properties set by the feature flag decision
Three structural properties are determined when an engineering team establishes its feature flag convention: how clearly the lifecycle expectation for each flag is defined and enforced as the team and flag inventory grow, how robustly the targeting model is coupled to the attribute systems it depends on and how that coupling is maintained as those systems evolve, and how consistently the flag inventory distinguishes temporary rollout mechanisms from permanent configuration toggles and dead flags from active ones. None are labeled as decisions at the time the first flag is added — they emerge from the implicit pattern the first flag establishes, which becomes the convention every subsequent flag follows without a formal record of what the convention expects of the engineers who follow it.
Property 1: The flag lifecycle and the technical debt accumulation surface. Feature flags are introduced as temporary instruments — code paths controlled by an external configuration so that deployment and enablement can be decoupled. The temporary nature of the instrument is structural: a flag that has been in the always-on state for twelve months is not enabling a gradual rollout; it is permanently enabling a code path that should have been made unconditional and the flag removed. The failure to remove flags after rollout completes is not primarily a discipline failure — it is a documentation failure. The engineer who adds a flag knows what it controls, knows when the rollout is complete, and in principle knows when the flag is ready to be removed. The engineer who joins the team two years later cannot know any of those things from the flag name, the description field that was left blank, or the commit message that says "add feature flag." The technical debt accumulation surface is the full set of flag-gated code paths in the codebase without documentation of their operational status. Each flag without a lifecycle record adds to that surface. The surface compounds: a large undocumented flag inventory makes it expensive to investigate any individual flag before removing it, which makes cleanup less likely, which makes the inventory larger, which makes cleanup more expensive. The flag lifecycle decision record closes the surface by specifying the lifecycle contract at the point when it is cheapest to specify it — before the flag is created — and by making the documentation a required artifact of flag creation rather than an optional improvement to be added later. The decisions never written down in the feature flag domain are not the decisions about which flag platform to use or which features to gate — those are visible in the codebase and the platform dashboard. They are the naming convention the first flag established, the cleanup expectation that was "after we're confident" and never formally defined, and the ownership model that was implicit team knowledge until the team grew past the size where implicit knowledge is reliable. The new CTO onboarding problem in the feature flag domain is specific: the incoming technical leader finds a flag inventory with 240 entries and no way to determine which are temporary, which are permanent, which are safety nets for migrations completed eight months ago, or which are controlling billing behavior for a customer segment whose existence is not documented anywhere in the flag platform. The feature flag ADR makes the inventory's operational structure explicit and auditable without an eight-month investigation to reconstruct it.
Property 2: The targeting model and the rollout correctness failure mode. Feature flag targeting rules are written at a specific point in time against a specific understanding of the attribute model — the plan types that exist, the user roles that are valid, the account tiers that the billing system produces. The targeting model is correct at the time it is written and incorrect at some future time after the attribute system it references has changed without the targeting rules being updated. The failure mode is not an error in flag evaluation — the SDK evaluates the rule correctly against the attribute it finds; there is no error, no alert, no indication in the dashboard that the targeting rule no longer produces the intended evaluation; the feature silently changes behavior for the users affected by the attribute change, and the only signal is a downstream effect that must be correlated with the attribute change before the root cause is clear. The structural property set by the targeting model decision is the coupling strength between the flag evaluation rules and the attribute systems they depend on: tight coupling means the targeting rules are reviewed and updated as part of every attribute system migration; loose coupling means the targeting rules are updated after a silent failure surfaces. The access control model decision record connects at the attribute contract layer: feature flag targeting rules that reference access control attributes — user roles, permission scopes, account tier — must be coupled to the same source of truth that the access control system uses; if the access control system changes an attribute value, the flag targeting rules that reference that attribute must be updated in the same migration; a flag that grants access to a premium feature based on a role attribute that the access control system has restructured is both a feature rollout failure and an access control failure. The API security decision record connects at the API capability gating layer: feature flags used to gate API capabilities — new endpoint versions, new authentication mechanisms, new rate limit tiers — must treat the API's attribute model as the system of record they depend on; when the API authentication model changes, flags that gate API access must be reviewed and updated in the same migration, not discovered as broken after the API authentication change has been deployed.
Property 3: The flag cleanup policy and the dead-flag density failure mode. Dead flags — flags in always-on or always-off state whose associated rollout is complete and whose code paths have not been cleaned up — are not operationally neutral. They represent code paths that exist in the codebase, are executed by the runtime, and diverge from the maintained code paths with every change made to the maintained paths that is not mirrored to the unmaintained ones. The dead-flag density failure mode is not that any individual dead flag causes a problem — it is that a high density of dead flags produces a codebase where the operational status of code paths is routinely assumed rather than verified, where the assumption accumulates until a security audit, a refactor, or a regression forces an inventory of what is actually executing in production. The structural property set by the flag cleanup policy is the rate at which the dead-flag density is allowed to grow: a cleanup policy with a defined cadence, a defined dead-flag criterion, and a defined removal process holds the density below the threshold at which the accumulation becomes a systemic risk; the absence of a cleanup policy allows the density to grow until the cost of establishing the operational status of any individual flag exceeds the cost of leaving it in place, producing a ratchet that terminates only when an external event forces the audit. The multi-tenant data isolation decision record connects at the code path isolation layer: feature flags used to gate multi-tenant isolation mechanisms — per-tenant query filtering, tenant-scoped storage paths, cross-tenant access controls — must be treated as high-priority cleanup targets after rollout completes; an always-on flag gating a tenant isolation fix leaves an always-off legacy path that bypasses the isolation fix in the codebase, a code-level vulnerability regardless of whether the flag evaluates to always-on in practice. The database backup verification decision record connects at the data integrity layer: flags gating database migration code paths have a specific cleanup urgency that general-purpose feature flags do not; a flag gating a database migration code path that has been in always-on state for more than thirty days is a permanent code fork of the database access layer that will receive no security patches, no schema migration updates, and no error handling improvements — and whose divergence from the maintained path grows with every database change made after the migration is pronounced complete.
What the founding session records and what it omits
The founding session that establishes feature flag usage — typically a single pull request by a single engineer solving an immediate deployment risk — records the integration: the SDK installed, the first flag created, the flag evaluation code added to the relevant conditional. If the engineer is thorough, it records the flag name and a brief description in the flag platform. What the founding session does not record is the lifecycle contract — when a flag transitions from rollout to always-on is not a definition of "cleanup ready"; it is, without documentation, a definition of "never cleaned up." It does not record the naming convention the first flag establishes, which becomes the pattern every subsequent flag follows until someone proposes a different convention and has the leverage to change the existing flags too. It does not record the targeting attribute contract — which systems produce the attributes the flag rules evaluate, who owns those systems, and what the protocol is for updating flag rules when attribute values change. And it does not record the flag classification taxonomy — whether a given flag is a temporary rollout gate, a permanent configuration toggle, or an experiment flag, each of which has a different expected lifetime and a different cleanup protocol.
These omissions are structurally similar across the decision record series: benign at founding, when they are covered by the founding team's shared context. The naming convention omission is not a problem when the platform has four flags added by two engineers who discuss the names in the same Slack conversation. The targeting attribute contract omission is not a problem when the attribute system is stable and the billing infrastructure has not yet evolved. The cleanup policy omission is not a problem when the flag inventory is small enough that every engineer knows what every flag does. The failure modes develop along three distinct timelines: the lifecycle debt accumulates linearly with team growth and flag addition rate, accelerating when engineers who created flags leave the team and take the implicit context with them; the targeting model failure develops at discrete migration events in the billing, identity, or account infrastructure — events that may be separated by months or years but which inevitably occur as the product evolves; and the dead-flag density failure compounds continuously from the first flag that should have been removed and was not, with the compounding rate accelerating as the inventory grows past the team's ability to mentally enumerate it.
The feature flag ADR closes these gaps by making the lifecycle contract explicit before the first flag is created, documenting the targeting attribute dependency at the time the targeting rule is configured, and specifying the cleanup cadence and dead-flag criteria before the inventory grows to a size where remediation cost exceeds cleanup cost. The product analytics decision record connects at the governance layer: the same pattern that produces 347 analytics event names when the naming convention is carried in a single engineer's head produces 240 feature flags with no ownership documentation when the flag convention is implicit; both are inventory governance failures with the same root cause — the founding context was shared knowledge, not documented policy, and shared knowledge does not survive team growth. The WhyChose extractor finds the feature flag discussions in your AI session history — the conversation where the flag naming convention was first sketched, the exchange where someone asked whether old flags would be cleaned up and the answer was "after the next sprint," the thread where the billing migration was planned and nobody mentioned that the flag targeting rules referenced the plan attribute — and surfaces those decisions so you can assess which assumptions still hold against the current flag inventory, attribute schema, and cleanup cadence.
The feature flag ADR: five sections
Section 1: Flag lifecycle contract and ownership model. Specify the lifecycle contract before the first flag is created: the four phases every temporary flag must pass through — creation with documented purpose, target rollout schedule, owner, and cleanup deadline; gradual enablement with a documented rollout plan specifying percentage checkpoints, metrics monitored at each checkpoint, and rollback threshold; full enablement defined as 100% rollout with a documented completion criterion that distinguishes "fully enabled" from "ready to clean up" — because a flag at 100% is not yet cleaned up and should not be treated as permanent simply because no rollout risk remains; and removal defined as deletion of the flag conditional from the codebase and deletion of the flag from the platform, not just setting the flag to always-on. Specify the cleanup deadline model: temporary flags must carry a cleanup deadline set at creation — not "after we're confident" but a specific date or a specific trigger condition such as "30 days after 100% rollout with no regression signals"; the cleanup deadline is surfaced in the quarterly flag inventory review; flags past their cleanup deadline are assigned to the on-call engineer for the week as a required cleanup task, not an optional backlog item. Specify the ownership model: every flag must carry an owner and a team; when an engineer leaves or changes teams, their flags must be reassigned as part of the offboarding process; the offboarding checklist for engineering roles must include a flag ownership transfer step that produces a new owner for each flag rather than leaving flags orphaned. Connect this section to the on-call rotation compensation decision record: flag cleanup is a recurring maintenance cost that accumulates when the lifecycle contract is not enforced; if the cleanup burden falls on on-call engineers without workload adjustment, the incentive to create flags without cleanup deadlines compounds the debt; the on-call scope and compensation model must account for flag cleanup as a legitimate operational task, not a discretionary cleanup that competes with new feature work.
Section 2: Flag classification taxonomy and permanent configuration policy. Specify the three flag types and their distinct expectations before the flag inventory grows to a size where distinguishing them requires historical research. Temporary rollout flags: flags added to decouple deployment from enablement for a specific feature, with a defined target end state of 100% enabled then removed and a cleanup deadline set at creation and enforced by the quarterly review. Permanent configuration flags: flags that represent intentional long-term behavioral variation across user segments — grandfathered pricing, regional compliance requirements, enterprise-only features that are permanently gated rather than rolled out — with a documented business reason, an explicit "permanent" classification in the platform metadata, and a review cadence to confirm the business reason still holds. Experiment flags: flags added for A/B or multivariate experiments, with a defined experiment hypothesis, a defined experiment duration, a primary metric, and a cleanup deadline set to the experiment end date plus a buffer for analysis; experiment flags must be cleaned up regardless of the experiment outcome — both the winner and the loser code paths result in a removed flag conditional, not a flag left in the always-on or always-off state indefinitely. The classification must be set at flag creation and must be a required field; platforms that do not support custom classification fields should encode the classification in the naming convention prefix or the description field. Connect this section to the compliance automation decision record: permanent configuration flags used for compliance purposes — GDPR consent gating, data residency routing, regulatory feature restrictions — must be classified explicitly as compliance flags with a review trigger tied to the compliance requirement they implement; when the regulatory requirement changes, the compliance flag must be updated as part of the compliance change response, not discovered in a later flag audit.
Section 3: Targeting attribute contract and attribute drift protocol. Specify the targeting attribute contract for every flag that uses targeting rules: the exact attribute name evaluated, the system of record that produces the attribute value, the current valid values and their semantics, and the team responsible for the source system. The contract must be documented at flag creation as part of the flag's metadata — either in the flag platform's description field or in a linked decision document — because the flag evaluation logic is only correct relative to the current attribute schema, and the attribute schema changes over time as the billing system, identity model, and account tier structure evolve. Specify the attribute drift protocol: a required flag audit step in any migration that renames, restructures, or removes attributes that flags evaluate; the migration plan for any billing system change, identity model restructure, account tier rename, or access control restructure must include a flag inventory search for rules referencing the affected attributes, and those rules must be updated before the attribute migration deploys, not after the first silent failure surfaces in a support ticket; the flag audit step is a blocking prerequisite for the migration deployment. Specify the integration test requirement for high-stakes targeting rules: flags gating premium features, billing-sensitive capabilities, compliance requirements, or enterprise-tier access must have integration tests asserting the expected evaluation result for each targeting rule given the current attribute values from the source system; these tests run in CI on every deployment and catch targeting drift introduced by attribute system changes before those changes reach production. Connect this section to the multi-tenant data isolation decision record: flags targeting rules that reference tenant identifiers, organization IDs, or account tier attributes in multi-tenant products must treat the tenant isolation model as the authoritative attribute system; a targeting rule that grants feature access based on an organization attribute must be consistent with the multi-tenant access control model — a flag rule that allows cross-tenant attribute evaluation or that does not scope its attribute lookup to the correct tenant context is both a rollout correctness failure and a tenant isolation failure.
Section 4: Flag cleanup cadence and dead-flag remediation protocol. Specify the cleanup cadence: a quarterly flag inventory review that produces a list of cleanup candidates using two criteria — flags in always-on or always-off state for more than 90 days without a "permanent" classification, and flags whose owner has left or changed teams without an ownership transfer. Each cleanup candidate is assigned to an engineer with a two-week window; the cleanup consists of removing the flag conditional from the codebase — preserving the always-on code path and deleting the always-off code path — deploying the removal, and then deleting the flag from the platform after confirming deployment stability. Specify the dead-flag remediation protocol: before removing any flag that has been in always-off state for more than six months, the always-off code path must be audited for security implications — SQL injection patterns, injection-vulnerable input handling, access control bypasses — before the code is deleted; the assumption that an always-off code path is operationally inert is not a substitute for a security review; the code exists in the repository, may have existed in production at some point, and may contain vulnerabilities that were patched in the maintained path but not in the unmaintained one. Specify the inventory health metrics: track the total flag count, the dead-flag fraction (always-on or always-off for more than 90 days as a percentage of total inventory), and the flag age distribution (median and 95th-percentile age of currently active flags); report these metrics in the quarterly review; a dead-flag fraction above 40% or a 95th-percentile flag age above 18 months is a remediation trigger requiring a dedicated cleanup sprint. Connect this section to the database backup verification decision record: flags gating database migration code paths must be treated as high-urgency cleanup targets with a cleanup deadline of 30 days after the migration reaches 100% rollout; the always-off code path in a database migration flag is not an inert rollback mechanism after 30 days — it is a diverged database access layer that will accumulate security and correctness debt with each change made to the maintained path; the cleanup urgency for database migration flags must be documented explicitly in the flag lifecycle record at flag creation, not reconstructed from the flag name and commit history after 18 months of divergence.
Section 5: Flag platform governance, naming convention, and SDK configuration. Specify the naming convention before the first flag is created: the naming pattern (kebab-case verb-noun phrases: "enable-sentiment-analysis", "migrate-query-engine-v2", "experiment-checkout-flow-v3"), the prohibited patterns (single-word names, names that do not indicate the gated capability, names that are identical to variable names in the codebase), and the required metadata fields (owner, team, classification, targeting attribute dependencies, cleanup deadline for temporary flags). The naming convention must be enforced in the code review process for pull requests that add flag evaluation logic — a flag with a non-conforming name should not pass code review, because the naming convention is the primary human-readable interface to the flag inventory and its consistency is what makes flag inventory audits feasible. Specify the SDK configuration: the default variation returned when the flag platform is unavailable or when a flag evaluation encounters an error; the default variation must be the safe state — for a flag controlling a security-sensitive capability, the default must be the disabled variation; for a flag controlling a database query path migration, the default must be the known-good path; documenting the default variation in the flag's ADR entry makes the fail-safe behavior explicit and prevents it from being changed without a deliberate decision. Specify the flag deletion order: delete the flag from the codebase before deleting it from the platform; a flag deleted from the platform while the code still references it evaluates to the SDK's default variation, which may not match the intended final state; deleting from the codebase first ensures the code's behavior is determined by the code, not by the absence of a platform entry. Connect this section to the audit log decision record: flag evaluation changes — flags enabled, disabled, retargeted, or deleted — are operationally significant events that should be recorded in the audit log alongside application-level events; a feature unexpectedly unavailable for a subset of users whose session started after a specific timestamp, correlated with a flag configuration change at that timestamp, is diagnosable in minutes if flag changes are in the audit log and in days if they are not; the audit log decision record must include flag configuration changes in its event taxonomy with the flag name, the changed attribute, the previous value, the new value, and the identity of the engineer who made the change.
FAQ
What should a feature flag decision record specify beyond which flag platform to use?
Four things. First, the flag lifecycle contract: the four phases every temporary flag must pass through — creation with documented purpose and owner, gradual enablement with a rollout plan, full enablement with a defined completion criterion, and removal with a cleanup deadline set at creation; without a lifecycle contract, flags accumulate in the always-on state indefinitely. Second, the targeting attribute contract: the specific attributes flag targeting rules may reference, the system of record for each attribute, and the protocol for updating flag targeting rules when the source system changes an attribute value; attribute drift between the billing system and the flag evaluation context is the primary cause of silent rollout failures. Third, the flag classification taxonomy: which flags are temporary rollout gates, which are permanent configuration toggles, and which are experiment flags; the three types have different cleanup expectations and ownership models; mixing them in a single unclassified inventory is the structural cause of the dead-flag density failure mode. Fourth, the dead-flag remediation protocol: the cadence for reviewing the flag inventory, the criteria for classifying a flag as dead, and the process for removing a dead flag safely — including a security audit of always-off code paths before deletion.
How do you prevent feature flag targeting rules from silently breaking as the product evolves?
Three requirements. First, document the attribute contract at flag creation: for each targeting rule, record which attribute is evaluated, which system is the source of truth for that attribute's value, the current valid values, and the team responsible for the source-of-truth system; this documentation is the input to the flag-update protocol when the source system changes. Second, require a flag audit step in any migration that renames or restructures the attributes flags reference: billing plan renames, identity model migrations, account tier restructures must include a flag inventory check as a required migration task, not an optional post-migration cleanup; the targeting rule that matched "enterprise" yesterday must be updated before the billing system renames the value, not after the first enterprise customer reports a missing feature. Third, implement integration tests for flag evaluation logic on high-stakes flags: flags gating premium features, billing-related capabilities, or compliance-required behaviors must have test coverage asserting the expected evaluation result for each targeting rule given the expected attribute values from the source system; these tests catch targeting drift before production users encounter the silent failure.
What is the right cadence for feature flag cleanup and how do you measure flag inventory health?
Cleanup cadence: a quarterly review of the flag inventory against two thresholds — flags in always-on state for more than 90 days with no documented reason for permanence, and flags with no owner because the original owner has left or changed teams. Both are removal candidates unless explicitly reclassified as permanent configuration. The quarterly review produces a removal list; each flag is assigned to an engineer with a two-week cleanup window and removed from both the codebase and the flag platform. Inventory health metrics: three numbers to track. First, total flag count and its rate of change — growing faster than the removal rate indicates the lifecycle contract is not being enforced. Second, dead-flag fraction — flags in always-on or always-off state for more than 90 days as a percentage of total inventory; above 40% indicates the inventory has become a configuration store rather than a rollout mechanism. Third, flag age distribution — median and 95th-percentile age; a 95th-percentile above 18 months indicates the long tail of flags is not being cleaned up regardless of the quarterly review cadence.
How do you safely remove a feature flag whose always-on code path has diverged from the disabled code path?
Four steps. First, establish which path is canonical: if the flag has been always-on for months, the enabled path is the production behavior; the disabled path is dead code — removal preserves the enabled path and deletes the disabled path. Second, audit the disabled path before deletion: even dead code paths must be reviewed for security implications; a SQL injection vulnerability in the disabled path is not a current exposure if the flag is always-on, but removing the flag without auditing the disabled path means the audit never happens. Third, confirm the flag evaluation in all environments: staging, production, and any canary environments may have the flag at different values; confirm the evaluation result in each environment before removing the conditional from code. Fourth, remove in two steps: remove the flag conditional and the disabled code path from the codebase, leaving the enabled path unconditional, deploy and confirm stability, then delete the flag from the platform; deleting from the platform before the code deployment creates a window where the code references a non-existent flag, resolving to the SDK default value which may not match the intended enabled state.