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:

  1. Runtime flags — Loaded from ~/.openclaude/feature-flags.json with environment variable overrides
  2. Build-time flags — Defined in 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 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:

  1. OPENCLAUDE_FEATURE_FLAGS_FILE — Preferred modern variable
  2. CLAUDE_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_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

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 integration
  • CONTEXT_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:

  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

// 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.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 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →