How OpenClaude's Feature Flag System Works: A Deep Dive Into Local Flag Resolution
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:
- Runtime flags — Loaded from
~/.openclaude/feature-flags.jsonwith environment variable overrides - Build-time flags — Defined in
scripts/build.tsand 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 reads feature flags from a JSON file in the user's home directory. By default, this path is:
~/.openclaude/feature-flags.json
Example flag file contents:
{
"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:
OPENCLAUDE_FEATURE_FLAGS_FILE— Preferred modern variableCLAUDE_FEATURE_FLAGS_FILE— Legacy fallback
// 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_lanterntengu_hive_evidencetengu_passport_quailtengu_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
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 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 integrationCONTEXT_COLLAPSE— Enables context window management features
Source Guard Validation
The test file 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 file. Two scenarios require cache invalidation:
- Manual edits to flags.json during development — Call
resetGrowthBook()or restart the process - Long-running processes needing fresh flags — Use
refreshGrowthBookFeatures()for background updates
Testing with Custom Flag Configurations
// 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 |
Core stub: file loading, env override precedence, public API |
scripts/build.ts |
Build-time flag definitions for bundler |
scripts/feature-flags-source-guard.test.ts |
Enforces source file existence for enabled build flags |
src/utils/settings/flagSettings.ts |
CLI flag handling for settings sources |
Summary
- Local-first design — Flags read from
~/.openclaude/feature-flags.jsonwith no external service dependency - Flexible overrides —
OPENCLAUDE_FEATURE_FLAGS_FILEenvironment 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →