Analytics & Plugins
This page covers the supporting services that provide feature flags, analytics tracking, plugin lifecycle management, policy enforcement, tips, and tool use summaries.
Feature Flags (GrowthBook)
Location: src/services/analytics/growthbook.ts
Claude Code uses GrowthBook for feature flags and A/B experiments. The integration supports remote evaluation with local caching for non-blocking reads.
User Attributes
Feature targeting uses a set of user attributes:
Unique user identifier.
Current session identifier.
Operating system platform.
Organization identifier for org-scoped targeting.
Subscription tier (Free, Pro, Max, Team, Enterprise).
Rate limit tier for capacity-based targeting.
CLI version for progressive rollouts.
Cached Feature Values
Two primary access patterns are used throughout the codebase:
// Boolean feature flag (cached, non-blocking)
getFeatureValue_CACHED_MAY_BE_STALE('feature_key', false)
// Dynamic config object (cached, non-blocking)
getDynamicConfig_CACHED_MAY_BE_STALE<ConfigType>('config_key', {})The _CACHED_MAY_BE_STALE suffix is a deliberate naming convention that reminds callers the value may be stale. Feature values are refreshed in the background but reads always return immediately from cache.
Blocking Initialization
For features that must be resolved before proceeding (rare), a blocking variant exists:
getDynamicConfig_BLOCKS_ON_INIT('config_key', defaultValue)Experiment Tracking
When a user is enrolled in an experiment, exposure data is stored and logged:
type StoredExperimentData = {
experimentId: string
variationId: number
inExperiment?: boolean
hashAttribute?: string
hashValue?: string
}Exposures are forwarded to the first-party event logging system for analysis.
Analytics Event Logging
Location: src/services/analytics/index.ts
The analytics service provides a zero-dependency event logging API. Events are queued until the analytics sink is attached during initialization.
Design Principles
- No import dependencies: Prevents import cycles across the codebase
- Queue-then-flush: Events logged before initialization are queued and replayed
- PII safety: String values require explicit type casting (
AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS) to verify no sensitive data is logged - Proto fields: Keys prefixed with
_PROTO_are routed to PII-tagged columns and stripped before general-access backends
Usage
import { logEvent } from '../analytics/index.js'
logEvent('tengu_api_retry', {
attempt: 3,
delayMs: 2000,
status: 429,
})Sink Architecture
The analytics sink routes events to multiple backends:
- Datadog: Real-time metrics and dashboards (
datadog.ts) - First-party event logging: Structured event export to internal analytics (
firstPartyEventLogger.ts) - Sink killswitch: Remote ability to disable analytics if needed (
sinkKillswitch.ts)
Plugin Installation Manager
Location: src/services/plugins/PluginInstallationManager.ts
The PluginInstallationManager handles automatic installation and reconciliation of plugins and marketplaces from trusted sources without blocking startup.
Marketplace Reconciliation
The manager reconciles declared marketplaces against materialized (installed) ones:
- Diff: Compare declared marketplaces against installed versions
- Install missing: Clone new marketplaces that aren't installed yet
- Update changed: Update marketplaces whose source has changed
- Refresh plugins: After new installs, refresh active plugins to pick up new tools, agents, LSP servers, and hooks
Installation Status
Plugin installation status is tracked in AppState.plugins.installationStatus:
type MarketplaceStatus = {
name: string
status: 'pending' | 'installing' | 'installed' | 'failed'
error?: string
}Post-Install Refresh
After marketplace reconciliation completes:
- New installs: Automatically refresh plugins (fixes "plugin-not-found" errors from the initial cache-only load)
- Updates only: Set
needsRefreshflag and show a notification to run/reload-plugins
Plugin Operations
The broader plugin system (in utils/plugins/) supports:
- Install: Clone a marketplace or install a specific plugin
- Enable: Activate a plugin so its tools, agents, hooks, MCP servers, and LSP servers are loaded
- Disable: Deactivate a plugin without removing it
- Remove: Uninstall a plugin and clean up its resources
- Reload: Refresh plugin caches and re-initialize dependent services (MCP, LSP)
Policy Limits Service
Location: src/services/policyLimits/
The policy limits service fetches organization-level restrictions from the API and uses them to disable CLI features. It follows the same patterns as remote managed settings.
Key Properties
| Property | Value |
|---|---|
| Fetch timeout | 10 seconds |
| Polling interval | 1 hour |
| Max retries | 5 |
| Cache file | ~/.claude/policy-limits.json |
| Failure mode | Fail-open (non-blocking) |
Eligibility
All console users with API keys are eligible for policy limits.
Only Team and Enterprise/C4E subscribers receive policy restrictions.
Policy limits fail open: if the fetch fails, the CLI continues without restrictions. This prevents a backend outage from blocking all users.
Tips Service
Location: src/services/tips/
The tips service shows contextual usage tips to help users discover CLI features.
Defines available tips with conditions for when they should be shown (e.g., multiple concurrent sessions, specific model in use, IDE detected).
Determines when and how often tips are displayed, preventing tip fatigue.
Tracks which tips have been shown to avoid repetition.
Tool Use Summary
Location: src/services/toolUseSummary/toolUseSummaryGenerator.ts
The tool use summary generator creates human-readable one-line summaries of completed tool batches. Primarily used by the SDK to provide high-level progress updates to clients.
How It Works
- Receives a list of tools with their inputs and outputs
- Sends a prompt to the Haiku model with a system prompt focused on brevity
- Returns a git-commit-style summary (under 30 characters)
Example Summaries
Searched in auth/
Fixed NPE in UserService
Created signup endpoint
Read config.json
Ran failing testsThe system prompt instructs the model to use past tense, keep the most distinctive noun, and drop articles and connectors.
export async function generateToolUseSummary({
tools,
signal,
isNonInteractiveSession,
lastAssistantText,
}: GenerateToolUseSummaryParams): Promise<string | null>