Back to Explore
GuideVibeFix 编辑部Updated Oct 17, 2026

Don't Ship on Prayers: Feature Flags and Emergency Kill-Switches for Vibe Projects

Feature flags decouple deploy from release: code ships first, features turn on later. Four flag types and a selection matrix, a one-table minimal implementation, the fallback iron rule (new features fail-closed, kill-switches freeze), flag-rot governance, and how flags pair with canary releases.

Real photo of an industrial emergency stop button on a control panel

This article is about one thing only: feature flags — controlling features after launch with switches instead of prayers. Boundaries first:

  • vs "Don't Ship It All at Once: Canary Releases": that one is about traffic-splitting progressive rollout (10%→50%→100%); this one is about switch-style control — who gets the feature by user, tenant, or percentage, with one-click kill. Different mechanism; no traffic splitting or rollback here.
  • vs "Your First Pipeline as a Solo Dev": that one is "how to ship"; this one is "how to govern after shipping" — flags are runtime governance post-release.
  • vs the incident runbook guide: killing via flag is move #1 in the runbook; this article expands the flag system itself, not the incident process.

One-line definition: a feature flag is an if in your code, except the condition isn't hardcoded — it's read from outside at runtime. Deploy and release decouple: code ships first, features turn on later; when things break you don't roll back, you flip the switch.

Three real problems flags solve

Match the scenarios before the implementation. Flags aren't a silver bullet; they solve exactly three problems:

Problem 1: new features you're afraid to turn on for everyone. Your AI built a new feature in two days; it runs fine in staging, and production has 100k users. Full rollout? Heart rate up. Flags let you open it to 5% of users, watch for two days, then expand to 50%, 100%. Note the difference from canary releases: canary is the traffic dimension (the same person may or may not hit it next time); flags are the identity dimension (this user has it, consistently).

Problem 2: incidents needing second-level hemostasis. 3 AM, the new feature is hammering the database. A rollback means redeploying — 10 minutes minimum; flipping a flag means changing one config line — seconds. The flag is your big red button, and it should be line #1 of the incident runbook.

Problem 3: paid features gated by plan. Free tier gets no AI summary, Pro does. The traditional way hardcodes if (plan === 'pro') — every plan tweak needs a code change and a deploy. With flags, plan rules become runtime config; ops (or you) click twice in the admin panel. A natural partner to paywall design: the paywall decides "what price," flags decide "who gets it."

Four flag types and the selection matrix

Not all flags look alike. By control dimension, four kinds:

TypeHow it opensTypical scenarioSolo-dev advice
Boolean flagGlobal on/offFeature master switch, incident killMust-have, simplest
Percentage rollout10%→50%→100% of usersGradual feature openingOnly past 10k users
Identity targetingNamed users/tenants/email domainsBeta, key accounts firstBuild when you have B2B customers
Plan gatingReads the user's subscriptionPaid feature controlBuild when you have a paywall

The selection logic is in the decision chart at the end; in one line: start with boolean flags, add types as incidents teach you. No incidents yet? Skip percentage rollout. No B2B customers? Skip tenant targeting. The flag system itself rots (see section 5) — more types, faster rot.

Minimal implementation: start with one DB table

A solo team doesn't need LaunchDarkly. Version one is a single database table:

-- feature_flags table
CREATE TABLE feature_flags (
  key         TEXT PRIMARY KEY,       -- 'new_checkout'
  enabled     BOOLEAN NOT NULL DEFAULT FALSE,
  rollout_pct INTEGER NOT NULL DEFAULT 0,     -- 0-100
  targets     JSONB NOT NULL DEFAULT '[]',    -- ["user:123", "tenant:acme"]
  updated_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_by  TEXT NOT NULL,                  -- who changed it, for audit
  expires_at  TIMESTAMPTZ                     -- expiry, anti-rot
);

-- read (with local cache, see below)
-- SELECT enabled, rollout_pct, targets FROM feature_flags WHERE key = 'new_checkout';

Three points on the read path:

  1. Cache: flags are read hot (possibly every request) — don't hit the DB each time. Cache in memory for 30–60s with active invalidation on change. Note the tension with "second-level kill": during an incident you want immediate effect, so the kill path must be "write DB + synchronously clear cache," not waiting for natural expiry.
  2. Fallback: reading the flag itself can fail (DB failover, cache stampede). Default on or off on failure? That's the next section's topic — spoiler: decide by blast radius, no silver bullet.
  3. Audit: the updated_by column isn't decoration. Who turned off which flag when is the single most important log line in a postmortem.

When you outgrow this, consider Unleash (self-hosted, open source) or LaunchDarkly (SaaS). Migration is cheap because the interface is already abstracted: business code only calls flags.isEnabled('new_checkout', user) — whether a DB table or a third-party service sits behind it, the business never notices.

Make AI-generated code flag-first by default

Vibe coders have a bespoke pain: you ask the agent for a new feature and it hardcodes everything — if (user.plan === 'pro') flows freely from the model and every change needs a deploy. One prompt constraint fixes it at the root:

## Feature flag convention (read before every new feature)
1. Every user-visible new feature MUST be wrapped in flags.isEnabled('exp_yyyymmdd_name', user). No hardcoded conditions.
2. Flag key naming: exp_ + date + feature name; fill expires_at at creation (default 30 days out).
3. Read-failure fallback: new features default off; kill-switches use last-known value (never blanket return false).

Drop this into the project's AGENTS.md or every session's context. The effect: every line of new-feature code the agent generates is born flaggable, gradual-rollout-able, and killable in seconds. You stop hunting "hardcoded spots" in review because the constraint is front-loaded. That's turning flags from an "ops habit" into a "code convention" — and conventions are the only thing that scales against AI-generated code.

The fallback iron rule: on read failure, default on or off?

This is the detail AI-generated flag code most often misses — and the line between a non-event and a disaster. Scenario: your flag service hiccups (DB failover, cache stampede) and isEnabled() throws or times out. What now?

Flag typeDefault on failureWhy
New-feature experience flagOff (fail-closed)Better users don't see the feature than half-baked logic running wild in a degraded state
Incident kill-switch (e.g., degradation flag)Freeze at last known stateFlipping a kill-switch mid-incident causes second-order damage; use the locally cached last value
Paid gating flagOff (fail-closed)Fail-open gives free users paid features with no audit trail afterward
Ops/debug flag (e.g., verbose logging)OffA debug flag flipping on during failure can fill disks or leak sensitive logs

Note the second row's special case: a kill-switch's fallback is "freeze," neither on nor off. Implementation: always keep the last successfully read value in local memory and use it on read failure. AI-generated flag code is almost always catch { return false; } one-size-fits-all — for a kill-switch, that blanket cut reopens the wound being stitched. When you see a catch block on flag reads in review, ask "what does this default to" — it blocks a whole class of incidents.

Flag rot governance: mandatory review 30 days after launch

A flag system's worst enemy isn't failure — it's forgetting. Every flag launches "temporary"; three months later the codebase holds 40 ifs nobody dares delete. That's flag rot. Three countermeasures:

  1. Naming convention: exp_202610_checkout_v2 beats new_stuff by a mile. Date + purpose in the name means you still know what it does in three months.
  2. Expiry: expires_at is required at creation (see the schema above). After expiry, CI fails loudly and forces a decision: delete the flag (feature fully rolled out — remove the if), or renew (with a written reason).
  3. 30-day review: a recurring calendar event to walk the flag list monthly. The test is simple: when was this flag last touched? Untouched for 60+ days means it's either fully rolled out (delete) or useless (delete).

Deleting a flag needs care too: first confirm it's currently "fully on," then remove the if branch in code (keep the on-side), ship and verify, and only then delete the DB row. Never reverse the order — deleting the DB row first triggers the read-failure fallback, and if that fallback is "off," you've just caused an incident.

Pairing with canary releases: flags answer "who," canary answers "how much"

The two get conflated constantly, but they're orthogonal:

Feature flagsCanary releases
Control dimensionIdentity (who)Traffic (how much)
Typical actionOpen for Pro users / kill a broken featureShip the new version to 10% of traffic
Rollback speedSeconds (config change)Minutes (traffic shift)
Applies atFeature levelVersion level

The field combo: on a major release, canary the new version to 10% of traffic while keeping aggressive features default-off behind flags; after clean observation, flip flags first (feature-level ramp), then expand canary (traffic-level ramp). Two independent safety nets, each with its own rollback path. The mnemonic: flags answer "who can use it," canary answers "how much traffic."

Solo launch checklist

Check before shipping — don't launch missing any:

  • ☐ Every flag has expires_at and a dated name
  • ☐ Read-failure fallback decided per flag per the table above (kill-switches use "freeze" especially)
  • ☐ The kill path is "write DB + synchronously clear cache," verified to take effect in seconds
  • ☐ The updated_by audit field is actually read (at least findable in postmortems)
  • ☐ A recurring monthly flag-review calendar event exists
  • ☐ Business code only calls flags.isEnabled(), never the DB table directly (so Unleash/LaunchDarkly can replace it later)

Feature flags are the kind of thing that feels redundant on quiet days and life-saving on bad ones. Their cost is one table and a little discipline; their payoff is sleep at 3 AM. Don't ship on prayers — ship on switches.

Views 0Comments 0

Comments (0)

ME
0/1000
Loading comments...
Browse projectsPublish your project

Related articles

Real photo of a programmer examining code on a monitor
Guide
Stop Running JSON.parse Naked: Validating and Repairing AI Structured Output

A prompt is a suggestion, not a constraint. Four defensive layers for reliable AI structured output: native structured outputs (OpenAI vs Anthropic, verified against docs), zod validation with the strict/passthrough tradeoff, repair retries that feed raw errors back, and degraded fallbacks chosen by cost.

Backend EngineeringProject BuildingIndie Development
Real photo of a person writing in a notebook beside a laptop
Guide
The User Wrote 2,000 Words Then Lost Them: Draft Autosave for Vibe Projects

A user's 2,000 words must survive crashes, dead networks, and accidental tab closes. Three defensive layers — debounced input, localStorage safety net, versioned server persistence — plus optimistic-locking conflict UX (keep both, let the user choose), offline replay, and draft lifecycle.

Project BuildingDesign ExperienceIndie Development
Collaborative editor illustration: multiple colored cursors with name labels (Sophia, Charlie) editing the same document
Guide
Two People Editing the Same Line: Realtime Collaborative Editing for Vibe Projects

Two people editing the same line is a consistency problem, not a realtime problem — naive WebSocket JSON broadcast dies at three users. Scoped against the site's realtime-features and offline-sync guides, then deep: OT vs CRDT with a decision table (conclusion first: solo teams pick Yjs), the Y.Doc/Awareness/Provider trio (30-line client, 40-line authenticated server), room-level ACL plus snapshot backstops, production pitfalls, and a 5-item launch checklist.

Developer WorkflowData WorkflowsTeam Collaboration