# How OpenClaude's Feature Flag System Works: A Deep Dive Into Local Flag Resolution

> Discover how OpenClaude's feature flag system works. Learn about local flag resolution using JSON files, environment variables, and default overrides for efficient development.

- Repository: [Gitlawb/openclaude](https://github.com/Gitlawb/openclaude)
- Tags: deep-dive
- Published: 2026-09-08

---

**OpenClaude uses a lightweight, self-contained feature flag system that replaces the upstream GrowthBook analytics client by reading flags from a local JSON file with environment variable overrides and built-in open-build defaults.**

This article explains how the open-source Claude client manages runtime behavior toggles without external telemetry. The system combines a GrowthBook stub implementation with build-time flag guards to give developers full control over which features are enabled.

## Feature Flag Architecture Overview

The OpenClaude feature flag system operates through two distinct layers:

1. **Runtime flags** — Loaded from `~/.openclaude/feature-flags.json` with environment variable overrides
2. **Build-time flags** — Defined in [`scripts/build.ts`](https://github.com/Gitlawb/openclaude/blob/main/scripts/build.ts) and validated by source guards

This design ensures that experimental features can be toggled without recompiling, while critical compile-time dependencies are strictly enforced.

## Runtime Flag Resolution

### Local Flag Source File

The core stub in [`src/services/analytics/growthbook.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/analytics/growthbook.ts) reads feature flags from a JSON file in the user's home directory. By default, this path is:

```bash
~/.openclaude/feature-flags.json

```

Example flag file contents:

```json
{
  "tengu_hive_evidence": true,
  "experimental_new_ui": false,
  "tengu_sedge_lantern": true
}

```

### Environment Variable Overrides

Developers can redirect the flag file location using two environment variables. The system checks them in order of precedence:

1. `OPENCLAUDE_FEATURE_FLAGS_FILE` — Preferred modern variable
2. `CLAUDE_FEATURE_FLAGS_FILE` — Legacy fallback

```typescript
// Set custom flag path before running OpenClaude
process.env.OPENCLAUDE_FEATURE_FLAGS_FILE = '/path/to/custom-flags.json';

```

### Open-Build Default Overrides

For the open-source build, a hard-coded map named `_openBuildDefaults` enforces specific flags regardless of upstream defaults. As defined in the stub, these include:

- `tengu_sedge_lantern`
- `tengu_hive_evidence`
- `tengu_passport_quail`
- `tengu_coral_fern`

These defaults ensure core open-source functionality remains available even when upstream disables comparable features.

### Flag Resolution Precedence

When `_getFlagValue` resolves a flag, it follows this strict hierarchy:

```

environment-override path → local JSON file → open-build defaults → caller-provided default

```

This precedence guarantees that local configuration wins over defaults, while environment variables allow temporary overrides for testing.

## Public Flag API

The GrowthBook stub exports a minimal API surface used throughout the codebase:

| Function | Purpose |
|----------|---------|
| `getFeatureValue_DEPRECATED` | Returns flag value with full resolution chain |
| `getFeatureValue_CACHED_MAY_BE_STALE` | Cached read (may return old value) |
| `getFeatureValue_CACHED_WITH_REFRESH` | Cached read with periodic refresh |
| `checkGate_CACHED_OR_BLOCKING` | Boolean gate check with cache or blocking fallback |
| `checkStatsigFeatureGate_CACHED_MAY_BE_STALE` | Alternative gate check interface |
| `resetGrowthBook()` | Clears cached flag object, forcing re-read |
| `refreshGrowthBookFeatures()` | Explicit cache refresh |

### Practical Usage Example

```typescript
import { 
  getFeatureValue_DEPRECATED,
  resetGrowthBook 
} from '@/services/analytics/growthbook';

// Query a flag with fallback default
const enableNewUI = getFeatureValue_DEPRECATED('experimental_new_ui', false);

// After editing flags.json, clear cache to pick up changes
resetGrowthBook();

```

## Build-Time Feature Flags

### Defining Compile-Time Flags

The [`scripts/build.ts`](https://github.com/Gitlawb/openclaude/blob/main/scripts/build.ts) file defines flags that affect bundler behavior. These differ from runtime flags because they control which code modules are included at compile time.

Common build-time flags include:

- `MCP_SKILLS` — Controls MCP (Model Context Protocol) skills integration
- `CONTEXT_COLLAPSE` — Enables context window management features

### Source Guard Validation

The test file [`scripts/feature-flags-source-guard.test.ts`](https://github.com/Gitlawb/openclaude/blob/main/scripts/feature-flags-source-guard.test.ts) enforces a critical safety constraint: **a build-time flag cannot be set to `true` unless its corresponding source file exists**.

Without this guard, the bundler would generate a missing-module stub, causing cryptic runtime errors. The guard runs during CI to catch configuration mistakes before release.

## Cache Management and Development Workflow

### When to Reset or Refresh

The stub maintains an in-memory cache of the parsed [`feature-flags.json`](https://github.com/Gitlawb/openclaude/blob/main/feature-flags.json) file. Two scenarios require cache invalidation:

1. **Manual edits to flags.json during development** — Call `resetGrowthBook()` or restart the process
2. **Long-running processes needing fresh flags** — Use `refreshGrowthBookFeatures()` for background updates

### Testing with Custom Flag Configurations

```typescript
// Test setup example
import { resetGrowthBook } from '@/services/analytics/growthbook';

beforeEach(() => {
  process.env.OPENCLAUDE_FEATURE_FLAGS_FILE = '/tmp/test-flags.json';
  resetGrowthBook();
});

afterEach(() => {
  delete process.env.OPENCLAUDE_FEATURE_FLAGS_FILE;
  resetGrowthBook();
});

```

## Key Source Files

| Path | Responsibility |
|------|----------------|
| [`src/services/analytics/growthbook.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/analytics/growthbook.ts) | Core stub: file loading, env override precedence, public API |
| [`scripts/build.ts`](https://github.com/Gitlawb/openclaude/blob/main/scripts/build.ts) | Build-time flag definitions for bundler |
| [`scripts/feature-flags-source-guard.test.ts`](https://github.com/Gitlawb/openclaude/blob/main/scripts/feature-flags-source-guard.test.ts) | Enforces source file existence for enabled build flags |
| [`src/utils/settings/flagSettings.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/settings/flagSettings.ts) | CLI flag handling for settings sources |

## Summary

- **Local-first design** — Flags read from `~/.openclaude/feature-flags.json` with no external service dependency
- **Flexible overrides** — `OPENCLAUDE_FEATURE_FLAGS_FILE` environment variable redirects the flag source
- **Tiered resolution** — Env path → JSON file → open-build defaults → caller default
- **Minimal API** — Six core functions expose flag values and cache control
- **Build-time safety** — Source guards prevent enabling flags with missing implementation files
- **Zero telemetry** — The replacement stub eliminates all GrowthBook analytics calls while preserving compatibility

## Frequently Asked Questions

### Where does OpenClaude store its feature flags?

OpenClaude stores runtime feature flags in `~/.openclaude/feature-flags.json` in the user's home directory. This location can be overridden with the `OPENCLAUDE_FEATURE_FLAGS_FILE` environment variable to point to any valid JSON file path.

### What's the difference between runtime and build-time flags in OpenClaude?

Runtime flags are queried live from the JSON file through functions like `getFeatureValue_DEPRECATED` and can change without recompilation. Build-time flags defined in [`scripts/build.ts`](https://github.com/Gitlawb/openclaude/blob/main/scripts/build.ts) affect which code modules the bundler includes; changing these requires a rebuild and is protected by source guard tests.

### How do I force OpenClaude to reload feature flags during development?

Call `resetGrowthBook()` imported from `@/services/analytics/growthbook` to clear the in-memory cache. The next flag query will re-read the JSON file from disk. For periodic updates in long-running processes, use `refreshGrowthBookFeatures()` instead.

### Why does OpenClaude replace the standard GrowthBook client?

The OpenClaude project replaces the upstream GrowthBook analytics client with a local stub to eliminate external telemetry dependencies. This keeps the open-source build fully self-contained while maintaining API compatibility for existing code that expects GrowthBook-style flag interfaces.