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
enableConfigs()— load settings.json system (ininit.ts)applySafeConfigEnvironmentVariables()— set env vars before first TLSapplyExtraCACertsFromConfig()— CA certificates- Remote settings promises initialized (non-blocking)
- Network configured (mTLS, proxy agents)
- GrowthBook initialized asynchronously (disk cache provides immediate values)
Cross-references
- piebald — build-time + runtime flag architecture
- service-layer — policy limits and remote settings services
- entrypoint-system — init sequence for config loading