DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Feature Flags with Claude Code: Safe Deployments with Toggle Management

Feature flags let you deploy code without immediately exposing new features to all users. Claude Code can design a complete feature flag system — from definition to percentage rollouts — when given the right patterns.


CLAUDE.md for Feature Flag Standards

## Feature Flag Rules

### When to use (required)
- Any new feature affecting production behavior
- A/B tests
- Staged rollouts (5% → 50% → 100%)

### Implementation rules
- Flag definitions: src/config/flags.ts (single source of truth)
- Flag names: snake_case (new_checkout_flow, ai_recommendations)
- Each flag must have: creation date, owner, expiry date in comment
- Default value: always false (opt-in)
- Maximum lifetime: 3 months → then delete

### Flag types
1. Release flags: turn feature on/off (delete when fully released)
2. Experiment flags: A/B tests (delete after experiment ends)
3. Operational flags: emergency kill switches (may persist longer)

### Prohibited
- if (process.env.NODE_ENV === 'development') branching (use flags instead)
- Version numbers in flag names (new_checkout_v2 → new_checkout)
- Flags abandoned for > 90 days
Enter fullscreen mode Exit fullscreen mode

Generating the Feature Flag System

Generate a simple feature flag system.

Requirements:
- Environment-variable based (no Redis dependency)
- TypeScript with full type safety
- Per-user enablement (enable for specific user IDs)
- Percentage rollout (enable for X% of users)

Generate these files:
- src/config/flags.ts (flag definitions)
- src/lib/featureFlag.ts (flag engine)
- src/middleware/flagContext.ts (attach flag context to requests)
Enter fullscreen mode Exit fullscreen mode

Generated Flag Definitions

// src/config/flags.ts
export const FLAGS = {
  /**
   * New single-page checkout flow
   * Created: 2026-01-15 @developer1
   * Expires: 2026-04-15
   */
  new_checkout_flow: {
    defaultValue: false,
    description: 'New checkout UI (3-step → 1-page)',
    rolloutPercentage: 20, // Enable for 20% of users
  },

  /**
   * AI-powered product recommendations
   * Created: 2026-02-01 @developer2
   * Expires: 2026-05-01
   */
  ai_recommendations: {
    defaultValue: false,
    description: 'Use AI model for product recommendations',
    enabledUserIds: ['user_123', 'user_456'], // Specific users only
  },
} as const;

export type FeatureFlag = keyof typeof FLAGS;
Enter fullscreen mode Exit fullscreen mode
// src/lib/featureFlag.ts
export class FeatureFlagService {
  isEnabled(flag: FeatureFlag, userId?: string): boolean {
    const config = FLAGS[flag];

    // Specific user list
    if ('enabledUserIds' in config && userId) {
      if (config.enabledUserIds.includes(userId)) return true;
    }

    // Percentage rollout (deterministic by userId)
    if ('rolloutPercentage' in config && userId) {
      const hash = [...userId].reduce((acc, c) => acc + c.charCodeAt(0), 0);
      return (hash % 100) < config.rolloutPercentage;
    }

    return config.defaultValue;
  }
}

export const featureFlags = new FeatureFlagService();
Enter fullscreen mode Exit fullscreen mode

Branching with Feature Flags

Use feature flags to switch between old and new recommendation logic.

Old: GET /products/:id  legacy recommendation service
New: when ai_recommendations flag is ON  call AI recommendation service

Requirements:
- Flag OFF has zero performance impact
- Response format stays identical regardless of flag
- Log flag value as metric
Enter fullscreen mode Exit fullscreen mode

Generated:

router.get('/products/:id', async (req, res) => {
  const useAI = featureFlags.isEnabled('ai_recommendations', req.user.id);

  const recommendations = useAI
    ? await aiRecommendationService.get(req.params.id)
    : await legacyRecommendationService.get(req.params.id);

  // Track flag usage as metric
  metrics.increment('feature_flag.evaluated', {
    flag: 'ai_recommendations',
    enabled: String(useAI),
  });

  res.json({
    product: await productService.get(req.params.id),
    recommendations,
  });
});
Enter fullscreen mode Exit fullscreen mode

Auto-Detecting Expired Flags with Claude Code Hooks

# .claude/hooks/check_flag_expiry.py
import json, re, sys
from datetime import datetime

data = json.load(sys.stdin)
content = data.get("tool_input", {}).get("content", "") or ""
fp = data.get("tool_input", {}).get("file_path", "")

if "flags.ts" not in (fp or ""):
    sys.exit(0)

today = datetime.now().date()
for match in re.finditer(r'Expires: (\d{4}-\d{2}-\d{2})', content):
    deadline = datetime.strptime(match.group(1), '%Y-%m-%d').date()
    if deadline < today:
        print(f"[FLAG] Expired flag detected (Expires: {match.group(1)})", file=sys.stderr)
        print("[FLAG] Remove this flag and the conditional code it controls", file=sys.stderr)
        sys.exit(1)  # Warning only — doesn't block

sys.exit(0)
Enter fullscreen mode Exit fullscreen mode

This runs every time flags.ts is modified, preventing expired flags from accumulating.


Summary

Design feature flags with Claude Code:

  1. CLAUDE.md — Define flag lifecycle, naming, expiry requirement
  2. Typed flag definitions — Expiry dates and owners in comments
  3. Percentage rollout — Deterministic user-hash based rollout
  4. Clean branching — Same response shape regardless of flag state
  5. Hook enforcement — Auto-detect expired flags on file edit

Code Review Pack (¥980) includes /code-review for detecting flag misuse — missing expiry dates, dead code from old flags, incorrect branching patterns.

👉 prompt-works.jp

Myouga (@myougatheaxo) — Claude Code engineer focused on safe deployment patterns.

Top comments (0)