Piebald (Feature Flags)
- Entity ID:
ent-20260409-9520cf5e76fa - Type:
concept - Scope:
shared - Status:
active - Aliases: piebald, feature flags, GrowthBook, feature gates
Description
Claude Code uses a hybrid two-layer feature flag system: build-time flags via the feature() function for dead code elimination (the "piebald" system), and runtime gates via GrowthBook for A/B testing, gradual rollouts, and dynamic configuration. Build-time flags remove entire subsystems from the compiled bundle. Runtime gates control access within a running build. Together they enable 80+ build-time feature gates and hundreds of runtime experiments.
Build-time feature flags
The feature() function
File: src/shims/bun-bundle.ts
const FEATURE_FLAGS: Record<string, boolean> = {
PROACTIVE: envBool('CLAUDE_CODE_PROACTIVE', false),
KAIROS: envBool('CLAUDE_CODE_KAIROS', false),
BRIDGE_MODE: envBool('CLAUDE_CODE_BRIDGE_MODE', false),
DAEMON: envBool('CLAUDE_CODE_DAEMON', false),
VOICE_MODE: envBool('CLAUDE_CODE_VOICE_MODE', false),
// ... 80+ more flags
}
export function feature(name: string): boolean {
return FEATURE_FLAGS[name] ?? false
}
In development (Bun), flags read from environment variables at runtime. In production builds (esbuild), the function is inlined as a boolean constant, enabling tree-shaking to remove entire unreachable code branches.
Dead code elimination pattern
The codebase requires a positive ternary pattern for tree-shaking to work:
// CORRECT — esbuild eliminates the entire branch
return feature('BRIDGE_MODE')
? isClaudeAISubscriber() && getFeatureValue_CACHED_MAY_BE_STALE('tengu_ccr_bridge', false)
: false
// WRONG — string literals leak into bundles
if (!feature('BRIDGE_MODE')) return
// ... code with inline strings ...
This pattern is documented explicitly in comments at src/bridge/bridgeEnabled.ts:25-30.
Build process
scripts/build-bundle.ts configures esbuild with:
- alias: { 'bun:bundle': 'src/shims/bun-bundle.ts' } — resolves the feature function
- treeShaking: true — eliminates dead branches
- define: { 'process.env.USER_TYPE': '"external"' } — removes internal-only code
- minify for production — further eliminates unreachable paths
Major build-time flags (80+ total)
Core subsystems:
- KAIROS / KAIROS_BRIEF / KAIROS_CHANNELS / KAIROS_DREAM — Kairos session management
- BRIDGE_MODE / CCR_AUTO_CONNECT / CCR_MIRROR — IDE Remote Control
- DAEMON — background daemon mode
- VOICE_MODE — voice input/output
- PROACTIVE — autonomous agent mode
- COORDINATOR_MODE — multi-agent coordination
- AGENT_TRIGGERS — triggered autonomous actions
AI/context features:
- ULTRAPLAN / ULTRATHINK — planning and extended thinking
- CONTEXT_COLLAPSE — context compression
- TRANSCRIPT_CLASSIFIER — AFK mode + auto-classification (30+ uses in code)
- HISTORY_SNIP — history compression
Skills/tools:
- MCP_SKILLS / MCP_RICH_OUTPUT — MCP integration
- FORK_SUBAGENT — subagent forking
- WEB_BROWSER_TOOL — web browser tool
- MONITOR_TOOL — monitoring/tracing
Infrastructure:
- BG_SESSIONS — background sessions
- SSH_REMOTE — SSH remote execution
- SELF_HOSTED_RUNNER / BYOC_ENVIRONMENT_RUNNER — runner modes
- PERFETTO_TRACING — performance profiling
- ABLATION_BASELINE — always false in external builds
Runtime GrowthBook gates
Integration
File: src/services/analytics/growthbook.ts (1156 lines)
GrowthBook provides A/B testing, gradual rollouts, and dynamic configuration. It's initialized asynchronously during startup (doesn't block main init) and refreshes every 6 hours (20 minutes for internal users).
Access patterns
Cached (preferred — non-blocking):
getFeatureValue_CACHED_MAY_BE_STALE<T>(feature, defaultValue): T
Pure read from in-memory cache. Falls back to disk cache in ~/.claude.json. Priority: env overrides → config overrides → in-memory → disk → default. Used on all hot paths.
Blocking with early-exit (entitlement gates):
checkGate_CACHED_OR_BLOCKING(gate): Promise<boolean>
Fast path if cache says true → return immediately. Slow path if false/missing → fetch fresh from server (max ~5s). Used for gates where stale false would unfairly block access.
Dynamic configuration (object values):
getDynamicConfig_CACHED_MAY_BE_STALE<T>(configName, defaultValue): T
Same as feature values but returns complex objects — used for configuration like batch sizes, thresholds, prompts.
User targeting attributes
GrowthBook receives attributes for experiment targeting:
- id / deviceID / sessionId — identity
- platform — win32/darwin/linux
- organizationUUID / accountUUID — org-level targeting
- userType — 'ant' vs 'external'
- subscriptionType / rateLimitTier — plan-level targeting
- appVersion — version-specific rollouts
- email — individual targeting
Initialization flow
- Create GrowthBook client with user attributes
client.init()— fetch payload fromhttps://api.anthropic.com/via remote eval- Process payload: transform API response format, cache feature values
- Sync to disk at
~/.claude.jsonundercachedGrowthBookFeatures - Set up periodic refresh (6h production, 20min internal)
- Notify subscribers via
onGrowthBookRefreshsignal
On auth changes (login/logout): destroy old client, recreate with fresh auth.
Disk cache
Features cached in ~/.claude.json:
{
"cachedGrowthBookFeatures": {
"tengu_ccr_bridge": true,
"tengu_bridge_repl_v2": false,
"tengu_1p_event_batch_config": { "key": "value" }
}
}
Wholesale replacement on refresh (not merge) — features deleted server-side are dropped from disk. Provides immediate fallback values before network fetch completes.
Developer overrides (internal only)
Environment variable:
export CLAUDE_INTERNAL_FC_OVERRIDES='{"my_feature": true, "my_config": {"key": "val"}}'
Config UI: via /config Gates tab. Stored in ~/.claude.json under growthBookOverrides. Fires onGrowthBookRefresh listeners when changed.
How both systems work together
A common pattern gates features at both layers:
export function isBridgeEnabled(): boolean {
return feature('BRIDGE_MODE') // Build-time: is the code even in this bundle?
? isClaudeAISubscriber() &&
getFeatureValue_CACHED_MAY_BE_STALE('tengu_ccr_bridge', false) // Runtime: is this user enabled?
: false
}
Build-time removes the code entirely from external builds. Runtime controls which users/orgs get access in builds where the code exists.
| Aspect | Build-time (feature()) |
Runtime (GrowthBook) |
|---|---|---|
| When evaluated | At bundle time | At runtime |
| Performance cost | Zero (dead code eliminated) | Minimal (cache lookup) |
| Use case | Major subsystem gates | A/B tests, gradual rollouts |
| Distribution | Environment variables at build | Remote server config |
| Update speed | Requires new build | Dynamic, refreshes every 6h |
| Kill switch | No | Yes (immediate via server) |
The "piebald" name
Piebald is also the name of a community-built tool that extracts prompt strings from Claude Code releases by analyzing compiled JavaScript bundles. It has tracked 141+ versions, providing a longitudinal view of how system prompts evolved. The ease of extraction from compiled JS is partly why anti-distillation defenses exist. The community tool and the internal feature flag system share a name but serve different purposes.
What depends on it
- Every subsystem — build-time flags gate which subsystems exist in a build
- Entrypoint system — feature gates control which CLI commands and modes are available
- System prompt assembly — ant-only prompt sections gated by
USER_TYPE - Service layer — many services are feature-gated (session memory, context collapse, etc.)
- Analytics — GrowthBook experiment exposure is logged for A/B analysis
Design trade-offs
| Decision | Trade-off |
|---|---|
| Two-layer system (build + runtime) | Flexible but complex — developers must know which layer to use |
| Positive ternary pattern required | Reliable tree-shaking, but unintuitive — wrong pattern silently leaks code |
| 80+ build-time flags | Fine-grained control, but flag proliferation makes the build matrix complex |
| 6-hour GrowthBook refresh | Low network overhead, but config changes take up to 6 hours to propagate |
| Disk cache for GrowthBook | Immediate fallback values, but stale data possible if disk is corrupted |
| Async GrowthBook init | Doesn't block startup, but first-turn features may use stale/default values |
Key claims
- 80+ build-time feature flags control which subsystems exist in the compiled bundle
- Positive ternary pattern is required for esbuild tree-shaking to eliminate dead branches
- GrowthBook provides runtime A/B testing with user attribute targeting (org, subscription, platform)
- Disk cache in ~/.claude.json provides immediate fallback before network fetch
- Both layers work together: build-time removes code, runtime controls access
Relations
rel-piebald-build: piebald --[configures]--> esbuild dead code eliminationrel-piebald-growthbook: piebald --[complements]--> GrowthBook runtime gatesrel-piebald-analytics: GrowthBook --[logs-to]--> analytics (experiment exposure)rel-piebald-entrypoint: feature flags --[gate]--> entrypoint-system modes
Sources
src-20260409-a62a26aa6e15, source code at src/shims/bun-bundle.ts, src/services/analytics/growthbook.ts, scripts/build-bundle.ts