mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
feat: enable GrowthBook local gate defaults for P0/P1 features
Add LOCAL_GATE_DEFAULTS mapping in growthbook.ts with 27 feature gate defaults (25 boolean + 2 object config). Insert local defaults into the fallback chain of all getter functions so they work regardless of whether GrowthBook is enabled or disabled: env overrides → config overrides → in-memory cache → disk cache → LOCAL_GATE_DEFAULTS → caller defaultValue P0 (local): keybindings, streaming tool exec, cron, JSON tools, ultrathink, explore/plan agents, deep link, immediate model switch P1 (API): session memory, auto memory, prompt suggestions, brief mode, verification agent, away summary, auto dream, idle return prompt Kill switches: 10 gates kept true to prevent remote disable New compile flags: AGENT_TRIGGERS, ULTRATHINK, BUILTIN_EXPLORE_PLAN_AGENTS, LODESTONE, EXTRACT_MEMORIES, VERIFICATION_AGENT, KAIROS_BRIEF, AWAY_SUMMARY Bypass all local defaults: CLAUDE_CODE_DISABLE_LOCAL_GATES=1
This commit is contained in:
@@ -416,6 +416,72 @@ function syncRemoteEvalToDisk(): void {
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Local default overrides for GrowthBook feature gates.
|
||||
*
|
||||
* When GrowthBook is not connected (e.g. no 1P event logging, no adapter),
|
||||
* these values are used instead of the hard-coded defaults (usually false).
|
||||
* This allows enabling features that have real implementations without
|
||||
* requiring a GrowthBook server connection.
|
||||
*
|
||||
* Set CLAUDE_CODE_DISABLE_LOCAL_GATES=1 to bypass these defaults.
|
||||
*
|
||||
* Categories:
|
||||
* P0 — Pure local features (no external dependencies)
|
||||
* P1 — Requires Claude API (works with any valid API key)
|
||||
* KS — Kill switches (default true, keep them true)
|
||||
*/
|
||||
const LOCAL_GATE_DEFAULTS: Record<string, unknown> = {
|
||||
// ── P0: Pure local features ──────────────────────────────────────
|
||||
tengu_keybinding_customization_release: true, // Custom keybindings
|
||||
tengu_streaming_tool_execution2: true, // Streaming tool execution
|
||||
tengu_kairos_cron: true, // Cron/scheduled tasks
|
||||
tengu_amber_json_tools: true, // Token-efficient JSON tools (~4.5% savings)
|
||||
tengu_immediate_model_command: true, // Instant /model, /fast, /effort during query
|
||||
tengu_basalt_3kr: true, // MCP instructions delta (send only changes)
|
||||
tengu_pebble_leaf_prune: true, // Session storage leaf pruning
|
||||
tengu_chair_sermon: true, // Message smooshing (merge adjacent blocks)
|
||||
tengu_lodestone_enabled: true, // Deep link protocol (claude://)
|
||||
tengu_auto_background_agents: true, // Auto-background agents after 120s
|
||||
tengu_fgts: true, // Fine-grained tool state in system prompt
|
||||
|
||||
// ── P1: API-dependent features ───────────────────────────────────
|
||||
tengu_session_memory: true, // Session memory (cross-session persistence)
|
||||
tengu_passport_quail: true, // Auto memory extraction
|
||||
tengu_moth_copse: true, // Skip memory index, use prefetched memories
|
||||
tengu_coral_fern: true, // "Searching past context" section
|
||||
tengu_chomp_inflection: true, // Prompt suggestions
|
||||
tengu_hive_evidence: true, // Verification agent
|
||||
tengu_kairos_brief: true, // Brief mode
|
||||
tengu_kairos_brief_config: { enable_slash_command: true }, // Brief /slash command visibility
|
||||
tengu_sedge_lantern: true, // Away summary
|
||||
tengu_onyx_plover: { enabled: true }, // Auto dream (memory consolidation)
|
||||
tengu_willow_mode: 'dialog', // Idle return prompt
|
||||
|
||||
// ── Kill switches (keep true to prevent remote disable) ──────────
|
||||
tengu_turtle_carbon: true, // Ultrathink extended thinking
|
||||
tengu_amber_stoat: true, // Built-in Explore/Plan agents
|
||||
tengu_amber_flint: true, // Agent teams/swarms
|
||||
tengu_slim_subagent_claudemd: true, // Slim CLAUDE.md for subagents
|
||||
tengu_birch_trellis: true, // Tree-sitter bash security analysis
|
||||
tengu_collage_kaleidoscope: true, // macOS clipboard image reading
|
||||
tengu_compact_cache_prefix: true, // Reuse prompt cache during compaction
|
||||
tengu_kairos_cron_durable: true, // Persistent cron tasks
|
||||
tengu_attribution_header: true, // API request attribution header
|
||||
tengu_slate_prism: true, // Agent progress summaries
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a local gate default. Returns undefined if not configured,
|
||||
* allowing the caller to fall through to the original defaultValue.
|
||||
*/
|
||||
function getLocalGateDefault(feature: string): unknown | undefined {
|
||||
if (process.env.CLAUDE_CODE_DISABLE_LOCAL_GATES) {
|
||||
return undefined
|
||||
}
|
||||
return LOCAL_GATE_DEFAULTS[feature]
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if GrowthBook operations should be enabled
|
||||
*/
|
||||
@@ -500,11 +566,13 @@ const getGrowthBookClient = memoize(
|
||||
const attributes = getUserAttributes()
|
||||
const clientKey = getGrowthBookClientKey()
|
||||
const baseUrl =
|
||||
process.env.CLAUDE_GB_ADAPTER_URL
|
||||
|| (process.env.USER_TYPE === 'ant'
|
||||
? process.env.CLAUDE_CODE_GB_BASE_URL || 'https://api.anthropic.com/'
|
||||
: 'https://api.anthropic.com/')
|
||||
const isAdapterMode = !!(process.env.CLAUDE_GB_ADAPTER_URL && process.env.CLAUDE_GB_ADAPTER_KEY)
|
||||
process.env.CLAUDE_GB_ADAPTER_URL ||
|
||||
(process.env.USER_TYPE === 'ant'
|
||||
? process.env.CLAUDE_CODE_GB_BASE_URL || 'https://api.anthropic.com/'
|
||||
: 'https://api.anthropic.com/')
|
||||
const isAdapterMode = !!(
|
||||
process.env.CLAUDE_GB_ADAPTER_URL && process.env.CLAUDE_GB_ADAPTER_KEY
|
||||
)
|
||||
if (process.env.USER_TYPE === 'ant') {
|
||||
logForDebugging(
|
||||
`GrowthBook: Creating client with clientKey=${clientKey}, attributes: ${jsonStringify(attributes)}`,
|
||||
@@ -537,7 +605,9 @@ const getGrowthBookClient = memoize(
|
||||
// remoteEval only works with Anthropic internal API, GrowthBook Cloud doesn't support it
|
||||
remoteEval: !isAdapterMode,
|
||||
// cacheKeyAttributes only valid with remoteEval
|
||||
...(!isAdapterMode ? { cacheKeyAttributes: ['id', 'organizationUUID'] } : {}),
|
||||
...(!isAdapterMode
|
||||
? { cacheKeyAttributes: ['id', 'organizationUUID'] }
|
||||
: {}),
|
||||
// Add auth headers if available
|
||||
...(authHeaders.error
|
||||
? {}
|
||||
@@ -691,12 +761,14 @@ async function getFeatureValueInternal<T>(
|
||||
}
|
||||
|
||||
if (!isGrowthBookEnabled()) {
|
||||
return defaultValue
|
||||
const localDefault = getLocalGateDefault(feature)
|
||||
return localDefault !== undefined ? (localDefault as T) : defaultValue
|
||||
}
|
||||
|
||||
const growthBookClient = await initializeGrowthBook()
|
||||
if (!growthBookClient) {
|
||||
return defaultValue
|
||||
const localDefault = getLocalGateDefault(feature)
|
||||
return localDefault !== undefined ? (localDefault as T) : defaultValue
|
||||
}
|
||||
|
||||
// Use cached remote eval values if available (workaround for SDK bug)
|
||||
@@ -754,7 +826,8 @@ export function getFeatureValue_CACHED_MAY_BE_STALE<T>(
|
||||
}
|
||||
|
||||
if (!isGrowthBookEnabled()) {
|
||||
return defaultValue
|
||||
const localDefault = getLocalGateDefault(feature)
|
||||
return localDefault !== undefined ? (localDefault as T) : defaultValue
|
||||
}
|
||||
|
||||
// Log experiment exposure if data is available, otherwise defer until after init
|
||||
@@ -776,7 +849,16 @@ export function getFeatureValue_CACHED_MAY_BE_STALE<T>(
|
||||
// Fall back to disk cache (survives across process restarts)
|
||||
try {
|
||||
const cached = getGlobalConfig().cachedGrowthBookFeatures?.[feature]
|
||||
return cached !== undefined ? (cached as T) : defaultValue
|
||||
if (cached !== undefined) {
|
||||
return cached as T
|
||||
}
|
||||
// Disk cache miss — use local gate defaults before falling back to
|
||||
// the caller's defaultValue. This covers the common case where
|
||||
// GrowthBook is "enabled" (user has an API key and analytics are on)
|
||||
// but has never connected to the remote server, so both in-memory
|
||||
// and disk caches are empty.
|
||||
const localDefault = getLocalGateDefault(feature)
|
||||
return localDefault !== undefined ? (localDefault as T) : defaultValue
|
||||
} catch {
|
||||
return defaultValue
|
||||
}
|
||||
@@ -823,7 +905,8 @@ export function checkStatsigFeatureGate_CACHED_MAY_BE_STALE(
|
||||
}
|
||||
|
||||
if (!isGrowthBookEnabled()) {
|
||||
return false
|
||||
const localDefault = getLocalGateDefault(gate)
|
||||
return localDefault !== undefined ? Boolean(localDefault) : false
|
||||
}
|
||||
|
||||
// Log experiment exposure if data is available, otherwise defer until after init
|
||||
@@ -841,7 +924,13 @@ export function checkStatsigFeatureGate_CACHED_MAY_BE_STALE(
|
||||
return Boolean(gbCached)
|
||||
}
|
||||
// Fallback to Statsig cache for migration period
|
||||
return config.cachedStatsigGates?.[gate] ?? false
|
||||
const statsigCached = config.cachedStatsigGates?.[gate]
|
||||
if (statsigCached !== undefined) {
|
||||
return statsigCached
|
||||
}
|
||||
// Neither cache has a value — use local gate defaults
|
||||
const localDefault = getLocalGateDefault(gate)
|
||||
return localDefault !== undefined ? Boolean(localDefault) : false
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -923,7 +1012,8 @@ export async function checkGate_CACHED_OR_BLOCKING(
|
||||
}
|
||||
|
||||
if (!isGrowthBookEnabled()) {
|
||||
return false
|
||||
const localDefault = getLocalGateDefault(gate)
|
||||
return localDefault !== undefined ? Boolean(localDefault) : false
|
||||
}
|
||||
|
||||
// Fast path: disk cache already says true — trust it
|
||||
|
||||
Reference in New Issue
Block a user