The Commented-Out Feature Flag: A Silent Bug That Breaks Gradual Rollouts

πŸ“– 3 minutes read

Feature flags are supposed to give you control. But a commented-out check can silently break that control across your entire application. Here’s the bug that took us 3 hours to find.

The Setup

We had a database column for a feature flag:


// Migration
Schema::table('products', function (Blueprint $table) {
    $table->boolean('enable_advanced_pricing')->default(false);
});

And a method on the model to check it:


class Product extends Model
{
    public function hasAdvancedPricing(): bool
    {
        // return $this->enable_advanced_pricing;
        return true; // TODO: Remove after testing
    }
}

See the problem?

The Bug

That “TODO” never got removed. The actual database check was commented out, and the method always returned true.

This meant:

  1. The feature flag column in the database was completely ignored
  2. All products behaved as if advanced pricing was enabled
  3. Setting the flag to false in the database had zero effect
  4. We thought we had gradual rollout control – we didn’t

How We Found It

We were debugging why pricing wasn’t working correctly for certain products. We checked the database:


SELECT id, name, enable_advanced_pricing 
FROM products 
WHERE id = 12345;

-- Result: enable_advanced_pricing = 0

Flag was off. But the code was still running advanced pricing logic. We dumped the method output:


dd($product->hasAdvancedPricing()); // bool(true)

Wait, what? The column says false, but the method returns true?

That’s when we found the commented-out check.

The Fix

Remove the hardcoded return value. Actually read the database:


class Product extends Model
{
    public function hasAdvancedPricing(): bool
    {
        return (bool) $this->enable_advanced_pricing;
    }
}

Or use a cast for cleaner syntax:


class Product extends Model
{
    protected $casts = [
        'enable_advanced_pricing' => 'boolean',
    ];

    public function hasAdvancedPricing(): bool
    {
        return $this->enable_advanced_pricing;
    }
}

The Lesson

Never hardcode feature flag return values. Not even for testing. If you need to force a value temporarily:

  1. Use environment variables: return config('features.force_advanced_pricing', $this->enable_advanced_pricing);
  2. Use a dedicated testing flag: if (app()->environment('testing')) return true;
  3. Better yet: use a proper feature flag service like Laravel Pennant

Catching This in Code Review

Look for these patterns in PRs:


// RED FLAG 1: Hardcoded return
public function hasFeature(): bool
{
    return true; // or false
}

// RED FLAG 2: Commented database check
public function hasFeature(): bool
{
    // return $this->feature_enabled;
    return config('features.default');
}

// RED FLAG 3: TODO with hardcoded value
public function hasFeature(): bool
{
    return true; // TODO: Fix this
}

Testing Feature Flags

Write tests that verify the flag actually controls behavior:


/** @test */
public function advanced_pricing_respects_database_flag()
{
    $product = Product::factory()->create([
        'enable_advanced_pricing' => false
    ]);
    
    $this->assertFalse($product->hasAdvancedPricing());
    
    $product->update(['enable_advanced_pricing' => true]);
    $product->refresh();
    
    $this->assertTrue($product->hasAdvancedPricing());
}

This test would have caught the commented-out check immediately.

Real Impact

This bug meant we couldn’t control the rollout of advanced pricing. All products got the new behavior simultaneously, instead of the gradual rollout we planned. It worked fine in development (where we wanted it on), but broke the production deployment strategy.

The fix took 2 minutes. Finding it took 3 hours.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *