Configuration and Feature Flags

How settings flow from defaults through user overrides to runtime behavior — and how the two-layer feature flag system controls what code ships and who gets access.

The two-layer feature flag system

Claude Code uses build-time flags and runtime gates together:

Build-time: feature()

80+ flags defined in src/shims/bun-bundle.ts. Each is a boolean constant inlined at build time by esbuild, enabling dead code elimination. Entire subsystems (KAIROS, BRIDGE_MODE, VOICE_MODE, DAEMON, COORDINATOR_MODE) are physically removed from the production bundle when disabled.

The positive ternary pattern is required for tree-shaking:

// CORRECT — esbuild eliminates dead branch
return feature('BRIDGE_MODE') ? expensiveCode() : false
// WRONG — string literals leak into bundle
if (!feature('BRIDGE_MODE')) return

Runtime: GrowthBook

A/B testing, gradual rollouts, and dynamic configuration via GrowthBook integration (src/services/analytics/growthbook.ts). User attributes (org, subscription, platform, user type) enable fine-grained targeting.

Access pattern: getFeatureValue_CACHED_MAY_BE_STALE(feature, default) — pure cache read from in-memory values, falling back to disk cache in ~/.claude.json. Refreshes every 6 hours (20 minutes for internal users).

How they combine

function isBridgeEnabled(): boolean {
  return feature('BRIDGE_MODE')                                    // Build-time: is code present?
    ? isClaudeAISubscriber() &&
        getFeatureValue_CACHED_MAY_BE_STALE('tengu_ccr_bridge', false)  // Runtime: is user enabled?
    : false
}

Build-time removes the code. Runtime controls who gets access in builds where the code exists.

Settings hierarchy

User settings flow through multiple layers with increasing specificity:

Layer Path Scope
Managed/policy ~/.claude/settings/policy.json Enterprise admin (highest priority)
User ~/.claude/settings.json User-wide defaults
Project .claude/settings.json Per-repository (checked into git)
Local .claude/settings.local.json Per-repo, per-developer (gitignored)

Lower layers override higher ones. Policy settings can enforce restrictions that users can't override (e.g., allowManagedHooksOnly, sandbox domains).

Remote managed settings

Enterprise customers get server-pushed configuration via src/services/remoteManagedSettings/: - Promise-based initialization (non-blocking startup) - HTTP ETag validation (304 Not Modified) for network efficiency - File-based disk cache survives restarts - 1-hour background polling - Security checks before applying remote overrides

Config schemas

Settings validated against Zod schemas (src/schemas/). Key schema areas: - Permission rules (tool access, deny rules) - Hook configuration (per-event matchers and commands) - MCP server definitions (stdio, SSE, HTTP, SDK transports) - Sandbox exclusions - Environment variables

The init sequence for configuration

  1. enableConfigs() — load settings.json system (in init.ts)
  2. applySafeConfigEnvironmentVariables() — set env vars before first TLS
  3. applyExtraCACertsFromConfig() — CA certificates
  4. Remote settings promises initialized (non-blocking)
  5. Network configured (mTLS, proxy agents)
  6. GrowthBook initialized asynchronously (disk cache provides immediate values)

Cross-references