# How featureFlagManager Controls Progressive Feature Rollouts in Desktop Commander

> Learn how Desktop Commander's featureFlagManager implements progressive feature rollouts by caching flags, fetching updates asynchronously, and providing a synchronous API with fallback defaults.

- Repository: [Eduard Ruzga/DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP)
- Tags: internals
- Published: 2026-07-09

---

**Desktop Commander's `featureFlagManager` is a lightweight singleton that enables progressive feature rollouts by caching flags locally, fetching updates asynchronously from a remote JSON endpoint, and exposing a synchronous API with fallback defaults.**

The `wonderwhy-er/DesktopCommanderMCP` repository implements a robust feature flag system that allows the development team to deploy new functionality, run A/B tests, and gradually roll out changes to specific user segments without requiring client redeployment. Understanding how `featureFlagManager` orchestrates these progressive feature rollouts reveals a pattern that balances immediate app startup performance with the flexibility of remote configuration.

## Architecture of the Feature Flag Manager

### Singleton Pattern and Local Cache

The feature flag system centers on a singleton instance exported from [`src/utils/feature-flags.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/feature-flags.ts). When the manager initializes, it immediately attempts to bootstrap from a local JSON cache located at [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json). This strategy ensures the application can start instantly with the last-known flag set, even when offline or experiencing network latency.

The constructor handles this cache loading synchronously:

```typescript
// src/utils/feature-flags.ts
constructor() {
  // Attempts to load cached flags immediately
  this.loadFromCache();
}

```

### Background Fetch with Timeout Protection

After loading cached data, the manager initiates an asynchronous fetch to the remote flag service at `https://desktopcommander.app/flags/v2/production.json`. This operation runs on a 5-minute timer and never blocks the main process, allowing the UI to render while flags update in the background.

To prevent stalled requests from hanging the client, the `fetchFlags` method implements a hard timeout using `Promise.race`:

```typescript
// src/utils/feature-flags.ts
private async fetchFlags(): Promise<void> {
  const timeoutPromise = new Promise((_, reject) => 
    setTimeout(() => reject(new Error('Timeout')), 3000)
  );
  
  const fetchPromise = fetch(this.remoteUrl);
  const response = await Promise.race([fetchPromise, timeoutPromise]);
  // Update in-memory map and cache
}

```

## The Feature Flag API

### Retrieving Flag Values

Client code queries flag states through the `get(flagName, default)` method, which returns the current value or a provided default. This design enforces a safe "off-by-default" stance—if a flag is absent or still loading from cache, the default value (typically `false`) prevents unfinished features from rendering.

```typescript
// src/utils/feature-flags.ts
public get<T>(flagName: string, defaultValue?: T): T | undefined {
  return this.flags.has(flagName) ? this.flags.get(flagName) : defaultValue;
}

```

### Waiting for Fresh Data

For critical paths that must know the definitive remote state—such as onboarding flows that should only appear after the flag is fetched—the manager exposes `waitForFreshFlags()`. This method returns a promise that resolves when the first successful network fetch completes or a safety timeout fires.

```typescript
// src/utils/feature-flags.ts
public async waitForFreshFlags(): Promise<void> {
  if (this.hasFetchedFresh) return;
  return new Promise((resolve) => {
    const check = () => {
      if (this.hasFetchedFresh) resolve();
      else setTimeout(check, 100);
    };
    check();
  });
}

```

### Cache State Inspection

The `wasLoadedFromCache()` method signals whether flag values originated from the local cache, enabling callers to treat "unknown" flags conservatively until fresh data arrives. This is particularly useful for analytics and debugging progressive rollouts.

```typescript
// src/utils/feature-flags.ts
public wasLoadedFromCache(): boolean {
  return this.loadedFromCache;
}

```

## Implementing Progressive Rollouts

### Safe Defaults and Lazy Loading

When a new flag (e.g., `welcomeOnboarding`) is added to the remote JSON, the manager gradually serves the flag to a subset of users based on weighted variants defined in the v2 format. Client code queries the flag via `featureFlagManager.get('welcomeOnboarding', false)`, ensuring that users who haven't received the new flag yet experience the existing behavior.

### Critical Path Blocking

In [`src/utils/welcome-onboarding.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/welcome-onboarding.ts), the onboarding flow demonstrates blocking for fresh flags before making UI decisions:

```typescript
// src/utils/welcome-onboarding.ts
import { featureFlagManager } from './feature-flags.js';

export async function maybeShowWelcome() {
  // Ensure we have the freshest flags before making a decision
  await featureFlagManager.waitForFreshFlags();

  const showWelcome = featureFlagManager.get('welcomeOnboarding', false);
  if (showWelcome) {
    // Display the welcome UI
  }
}

```

### A/B Testing Integration

The manager supports A/B testing through weighted variants in the remote payload. Modules such as [`src/utils/ab-test.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/ab-test.ts) import the manager and read experiment definitions to assign users to different UI versions:

```typescript
// src/utils/ab-test.ts
import { featureFlagManager } from './feature-flags.js';

export function getSearchMode(): 'legacy' | 'enhanced' {
  return featureFlagManager.get('useEnhancedSearch') ? 'enhanced' : 'legacy';
}

```

## Real-World Usage Examples

**Application Bootstrap:**

The manager initializes early in the application lifecycle in [`src/index.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/index.ts) to begin the background fetch without blocking startup:

```typescript
// src/index.ts
import { featureFlagManager } from './utils/feature-flags.js';
featureFlagManager.initialize();  // Starts cache load + background fetch

```

**Manual Refresh:**

During development or when immediate updates are required, you can force an immediate network fetch:

```typescript
await featureFlagManager.refresh();  // Forces immediate network fetch

```

## Summary

- **Local-first architecture**: The manager loads from [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) cache instantly, then updates from the network asynchronously.
- **Timeout-protected fetches**: A 3-second `Promise.race` timeout prevents network stalls from hanging the client.
- **Dual API design**: Synchronous `get()` calls for immediate rendering with defaults, plus `waitForFreshFlags()` for critical decisions.
- **Remote configuration**: Updates to `https://desktopcommander.app/flags/v2/production.json` propagate to users within 5 minutes without redeployment.
- **A/B test support**: Weighted variants in the v2 flag format enable gradual percentage-based rollouts.

## Frequently Asked Questions

### How does featureFlagManager handle offline scenarios?

The manager immediately falls back to the local [`feature-flags.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/feature-flags.json) cache on startup, ensuring the application functions normally offline. Network fetches occur in the background when connectivity returns, and the cache updates transparently without requiring user intervention.

### What happens if the remote flag service is slow or unresponsive?

A 3-second timeout in the `fetchFlags` method prevents slow responses from blocking the application. If the timeout fires, the manager continues using cached values and retries on the next 5-minute interval, maintaining stability while attempting to refresh.

### Can I force an immediate refresh of feature flags outside the 5-minute cycle?

Yes, the manager exposes a `refresh()` method that forces an immediate network fetch. This is useful during development or when implementing a "check for updates" button, though production code typically relies on the automatic background polling.

### How does the system support progressive rollouts to specific user percentages?

The v2 flag format supports weighted variants in the remote JSON payload. When fetching flags, the server can return different values based on user segmentation (e.g., 10% of users receive `true` for a new feature while 90% receive `false`). Client code queries the flag normally via `get()`, receiving their assigned variant without knowing the rollout percentage.