Feature Flags con Claude Code: rollouts seguros, experimentos y kill switches
Guía práctica de feature flags con Claude Code: rollout, experimentos, kill switches, targeting, métricas y limpieza.
Empieza por la regla operativa, no por el toggle
Una feature flag es un interruptor en tiempo de ejecución: puedes desplegar código y activar, desactivar o ampliar una funcionalidad después. El error de principiante no es escribir if (flag). El error serio es tratar igual una flag de release, una flag de experimento y un kill switch. Cada una tiene vida útil, dueño, métricas y limpieza diferentes.
Claude Code genera rápido la rama de UI, pero producción exige más: valor por defecto seguro, targeting context, límite entre evaluación de servidor y cliente, logs de exposición, métricas de guardrail, pasos de rollback y fecha para borrar las flags temporales. En los flujos de Masa con sitios de contenido y pequeños SaaS, el mejor prompt no empieza con “añade flags”; empieza con “qué se rompe, qué puedo apagar y cómo sabremos si el rollout está sano”.
Conviene anclar el diseño en documentación primaria. OpenFeature separa la API de evaluación que usa la app del provider que hay detrás y usa evaluation context para datos de usuario, app y entorno. LaunchDarkly documenta usos como release flags, experiment flags y kill switches. Unleash explica un ciclo de vida que pasa por Define, Develop, Production, Cleanup y Archived. La documentación de Claude Code también recomienda dar al agente una ruta clara de verificación.
Referencias usadas:
- OpenFeature introduction
- OpenFeature evaluation context
- LaunchDarkly creating flags
- LaunchDarkly targeting rules
- LaunchDarkly guarded rollouts
- Unleash feature flag lifecycle
- Claude Code best practices
Separa release, experimento y kill switch
Clasifica por vida útil antes de programar. Una release flag oculta trabajo incompleto, lo abre gradualmente y desaparece cuando llega al 100%. Una experiment flag prueba una hipótesis y debe registrar exposición y resultado. Un kill switch es un control de seguridad más permanente para fallos de APIs externas, picos de coste, servicios lentos de recomendación o automatizaciones peligrosas.
| Caso de producto | Tipo | Métrica de éxito | Acción si falla |
|---|---|---|---|
| Nuevo checkout para 25% de cuentas Pro | Release | Pago completado, errores de pago | Apagar checkout_v2_release |
| Comparar CTA en pricing | Experimento | Inicio de registro, clics de intención de pago | Parar experimento y servir control |
| Mover bloque affiliate dentro del artículo | Experimento | Clics de producto, lectura completa | Volver al footer del artículo |
| Desactivar recomendaciones por incidente de proveedor | Kill switch | Latencia p95, tasa 5xx | Apagar recommendations_enabled |
Para medir bien, combínalo con A/B testing con Claude Code y analítica con Claude Code. En un sitio monetizado, la CTA no es solo el clic: también protege calidad de AdSense, lectura completa, revenue affiliate e intención de consultoría. Para empezar en solitario, usa la guía gratuita; para equipos, el siguiente paso es la consultoría en inglés.
Configuración mínima y evaluator
No conviene empezar pegando la lógica de negocio a un proveedor concreto. La app debería evaluar por key, valor por defecto y context; después puedes cambiar el provider a LaunchDarkly, Unleash, OpenFeature, un JSON o un servicio interno. Este ejemplo cabe en flag-demo.ts y se ejecuta con npx tsx flag-demo.ts.
type FlagValue = boolean | string | number;
type FlagKind = "release" | "experiment" | "kill_switch";
type Plan = "free" | "pro" | "enterprise";
type Role = "user" | "admin";
type Operator = "equals" | "in";
type FlagContext = {
targetingKey: string;
plan: Plan;
country: string;
role: Role;
appVersion: string;
};
type FlagRule = {
attribute: keyof Omit<FlagContext, "targetingKey">;
operator: Operator;
values: string[];
value: FlagValue;
percentage?: number;
};
type FlagConfig = {
key: string;
kind: FlagKind;
enabled: boolean;
defaultValue: FlagValue;
offValue: FlagValue;
owner: string;
removeAfter?: string;
rules: FlagRule[];
};
const registry: Record<string, FlagConfig> = {
checkout_v2_release: {
key: "checkout_v2_release",
kind: "release",
enabled: true,
defaultValue: false,
offValue: false,
owner: "growth-platform",
removeAfter: "2026-07-15",
rules: [
{
attribute: "role",
operator: "equals",
values: ["admin"],
value: true,
},
{
attribute: "plan",
operator: "in",
values: ["pro", "enterprise"],
value: true,
percentage: 25,
},
],
},
pricing_copy_2026_06: {
key: "pricing_copy_2026_06",
kind: "experiment",
enabled: true,
defaultValue: "control",
offValue: "control",
owner: "monetization",
removeAfter: "2026-06-30",
rules: [
{
attribute: "country",
operator: "in",
values: ["JP", "US", "DE"],
value: "simple",
percentage: 50,
},
],
},
recommendations_enabled: {
key: "recommendations_enabled",
kind: "kill_switch",
enabled: true,
defaultValue: true,
offValue: false,
owner: "sre",
rules: [],
},
};
function bucketFor(flagKey: string, targetingKey: string): number {
const input = `${flagKey}:${targetingKey}`;
let hash = 0;
for (const char of input) {
hash = (hash * 31 + char.charCodeAt(0)) >>> 0;
}
return hash % 100;
}
function ruleMatches(
flagKey: string,
rule: FlagRule,
context: FlagContext,
): boolean {
const actual = String(context[rule.attribute]);
const matched =
rule.operator === "equals"
? actual === rule.values[0]
: rule.values.includes(actual);
if (!matched) return false;
if (rule.percentage === undefined) return true;
return bucketFor(flagKey, context.targetingKey) < rule.percentage;
}
export function evaluateFlag<T extends FlagValue = FlagValue>(
key: string,
context: FlagContext,
): T {
const flag = registry[key];
if (!flag) return false as T;
if (!flag.enabled) return flag.offValue as T;
for (const rule of flag.rules) {
if (ruleMatches(flag.key, rule, context)) {
return rule.value as T;
}
}
return flag.defaultValue as T;
}
const demoContexts: FlagContext[] = [
{
targetingKey: "user_001",
plan: "pro",
country: "JP",
role: "user",
appVersion: "1.8.0",
},
{
targetingKey: "user_002",
plan: "free",
country: "BR",
role: "admin",
appVersion: "1.8.0",
},
];
for (const context of demoContexts) {
console.log(context.targetingKey, {
checkout: evaluateFlag<boolean>("checkout_v2_release", context),
pricingCopy: evaluateFlag<string>("pricing_copy_2026_06", context),
recommendations: evaluateFlag<boolean>(
"recommendations_enabled",
context,
),
});
}
Los detalles importantes son deliberadamente simples: una flag desconocida cae a un fallback seguro, el rollout porcentual usa targetingKey estable y cada flag temporal tiene owner y removeAfter. El registry puede moverse a un panel, pero el contrato de la app se mantiene pequeño.
Evalúa en servidor y muestra en cliente
Todo lo relacionado con billing, autorización, cuotas, inventario o coste backend debe evaluarse en servidor. Las flags de cliente sirven para copy, layout, ayudas de onboarding o cambios visuales ya autorizados. No envíes reglas secretas al navegador ni uses botones ocultos como control de acceso.
type User = {
id: string;
plan: "free" | "pro" | "enterprise";
role: "user" | "admin";
};
type RequestLike = {
headers: {
get(name: string): string | null;
};
};
export function buildFlagContext(
user: User,
request: RequestLike,
): FlagContext {
return {
targetingKey: user.id,
plan: user.plan,
role: user.role,
country: request.headers.get("x-country") ?? "US",
appVersion: process.env.NEXT_PUBLIC_APP_VERSION ?? "dev",
};
}
export function getServerFlagSnapshot(context: FlagContext) {
return {
checkoutV2: evaluateFlag<boolean>("checkout_v2_release", context),
pricingCopy: evaluateFlag<string>("pricing_copy_2026_06", context),
};
}
type PricingFlags = {
pricingCopy: string;
};
export function PricingCta({ flags }: { flags: PricingFlags }) {
const label =
flags.pricingCopy === "simple"
? "Empezar con el plan gratuito"
: "Iniciar prueba gratis";
return <a href="/signup">{label}</a>;
}
Así React solo pinta un snapshot ya evaluado. Cuando pidas el cambio a Claude Code, escribe explícitamente que permisos y billing quedan en servidor, y que el cliente solo consume el resultado.
Rollout con observabilidad
Un rollout seguro no es “empieza al 1%” y ya. Necesitas calendario de aumento, métricas y umbrales de rollback. Unleash combina porcentaje, stickiness y restricciones. LaunchDarkly guarded rollouts conecta despliegues con métricas y permite pausar o revertir si hay regresión. Puedes copiar ese modelo aunque tu evaluator sea pequeño.
Registra tres capas: exposición, métrica primaria y guardrails. La exposición responde quién vio qué valor. La métrica primaria dice si mejoró el comportamiento deseado. Los guardrails detectan daño en velocidad, errores, calidad de ingresos, soporte o confianza.
type FlagExposure = {
flagKey: string;
value: FlagValue;
targetingKey: string;
route: string;
evaluatedAt: string;
};
export function trackFlagExposure(event: FlagExposure) {
console.log(
JSON.stringify({
event_name: "feature_flag_exposure",
...event,
}),
);
}
En checkout mira 5xx, pagos fallidos y tickets. En un blog monetizado, no mires solo clics affiliate: revisa lectura completa, bounce, Core Web Vitals y clics de intención de compra o consultoría. En funciones de IA, observa coste de tokens, latencia p95 y cuotas por usuario. Más clics con peor calidad comercial no es una victoria.
Fallos concretos
El primer fallo es asignar al azar en cada carga. Si el usuario refresca y cambia de A a B, los datos de exposición y conversión no sirven. Usa un targeting key estable.
El segundo es esconder premium solo en cliente. Que no haya botón en React no protege la API. Feature flag mejora UX, pero no sustituye autorización.
El tercero es un default inseguro. Una release flag desconocida suele tener que devolver false. Si un typo devuelve true, acabas de lanzar por accidente.
El cuarto es no borrar flags temporales. Meses después, checkout_v2_release será una rama que nadie entiende. Al terminar la decisión, abre el PR de cleanup.
El quinto es anidar demasiadas reglas. Flags padre, flags hijo y porcentajes solapados hacen difícil explicar quién ve realmente la función.
Prompts seguros para Claude Code
Claude Code puede leer archivos, editarlos y ejecutar pruebas. Dale límites y verificación desde el inicio.
Añade un workflow de feature flags a este repositorio.
La primera flag es checkout_v2_release para rollout gradual.
Restricciones:
- Billing y autorización se evalúan en servidor.
- Las release flags desconocidas devuelven false.
- El rollout porcentual usa targetingKey estable.
- El registry incluye owner y removeAfter.
- No modifiques archivos no relacionados.
Salida requerida:
- Registry mínimo y función evaluateFlag
- Tipo de evento de exposición
- Al menos 3 casos de producto
- Fallos y pasos de rollback
- Comandos de test ejecutados
Antes de mergear, usa un prompt de revisión:
Revisa esta implementación de feature flags.
Enfócate en defaults, frontera servidor/cliente, bucketing estable,
eventos de exposición, fechas de cleanup y rollback.
Lista hallazgos por severidad con archivos exactos.
Con esa estructura, Claude Code tiende a producir un sistema operable, no solo un toggle decorativo.
Cleanup también es parte del release
Cada flag empieza a envejecer el día que nace. Las release flags se borran tras el 100%. Las experiment flags se borran al elegir ganador. Los kill switches pueden quedarse, pero necesitan dueño, runbook y alertas. Incluye owner, removeAfter, métricas y PR de eliminación previsto en tu plantilla de pull request.
Verifiqué el evaluator de este artículo como demo TypeScript ejecutable: el mismo targetingKey cae en el mismo bucket, las flags desconocidas devuelven fallback seguro y el kill switch tiene off value explícito. La nota práctica de Masa en contenido monetizado es medir calidad además de clics. Empieza con una release flag, una experiment flag y un kill switch antes de introducir una plataforma completa.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Escalera de permisos de Claude Code para ampliar acceso sin perder control
Pasa de read-only a ediciones limitadas, comandos de prueba y checks de deploy con menos riesgo.
Claude Code Small PR Proof Pack: cambios pequeños que sí se pueden revisar
Un paquete de prueba para PRs de Claude Code: diff, checks, URL pública, CTA y rollback.
Gate de revisión antes del commit con Claude Code
Cómo revisar con Claude Code antes del commit: diff, build, URL pública, Gumroad, consultoría, tests y archivos ajenos.