71 KiB
Claude Code — Core Entry Points & Query System
Table of Contents
- entrypoints/cli.tsx — Bootstrap Dispatcher
- main.tsx — Full CLI Entry Point
- replLauncher.tsx — REPL UI Launcher
- entrypoints/init.ts — Initialization & Telemetry
- entrypoints/mcp.ts — MCP Server Entrypoint
- entrypoints/agentSdkTypes.ts — Agent SDK Public API
- entrypoints/sandboxTypes.ts — Sandbox Configuration Types
- entrypoints/sdk/coreSchemas.ts — SDK Core Zod Schemas
- entrypoints/sdk/coreTypes.ts — SDK Core TypeScript Types
- entrypoints/sdk/controlSchemas.ts — SDK Control Protocol Schemas
- query.ts — Core Async Query Loop
- QueryEngine.ts — Stateful Query Engine (SDK/Headless)
- query/config.ts — Query Configuration Snapshot
- query/deps.ts — Query Dependency Injection
- query/stopHooks.ts — Stop Hook Orchestration
- query/tokenBudget.ts — Token Budget Tracking
- context.ts — System & User Context Providers
- history.ts — Prompt History Management
- cost-tracker.ts — Session Cost Tracking
- costHook.ts — React Cost Summary Hook
- projectOnboardingState.ts — Project Onboarding State
- bootstrap/state.ts — Global Session State
- assistant/sessionHistory.ts — Remote Session History Pagination
entrypoints/cli.tsx — Bootstrap Dispatcher
Purpose
The very first module executed when a user runs claude. Acts as a lightweight bootstrap dispatcher that checks process arguments for known fast-paths before loading any heavy modules. Each fast-path dynamically imports only what it needs. The full CLI (main.tsx) is only loaded when no fast-path matches.
Key Flow
process.argv parsing
├── --version / -v → print MACRO.VERSION, exit (zero imports)
├── --dump-system-prompt → dump rendered system prompt (ant-only, DUMP_SYSTEM_PROMPT feature)
├── --claude-in-chrome-mcp → runClaudeInChromeMcpServer()
├── --chrome-native-host → runChromeNativeHost()
├── --computer-use-mcp → runComputerUseMcpServer() (CHICAGO_MCP feature)
├── --daemon-worker=<kind> → runDaemonWorker() (DAEMON feature)
├── remote-control|rc|remote|sync|bridge → bridgeMain() (BRIDGE_MODE feature)
├── daemon → daemonMain() (DAEMON feature)
├── ps|logs|attach|kill|--bg|--background → bg handlers (BG_SESSIONS feature)
├── new|list|reply → templatesMain() (TEMPLATES feature)
├── environment-runner → environmentRunnerMain() (BYOC_ENVIRONMENT_RUNNER feature)
├── self-hosted-runner → selfHostedRunnerMain() (SELF_HOSTED_RUNNER feature)
├── --worktree + --tmux → execIntoTmuxWorktree()
└── (default) → startCapturingEarlyInput() → import main.tsx → cliMain()
Top-Level Side Effects (at module load time)
| Side Effect | Purpose |
|---|---|
process.env.COREPACK_ENABLE_AUTO_PIN = '0' |
Prevent yarnpkg from being added to package.json |
process.env.NODE_OPTIONS += '--max-old-space-size=8192' |
CCR environment (16GB containers) heap size |
ABLATION_BASELINE flag |
Sets multiple CLAUDE_CODE_* env vars for harness-science L0 ablation |
Exports
// No named exports — module executes main() IIFE at bottom
async function main(): Promise<void> // internal, not exported
Feature Flags Checked
DUMP_SYSTEM_PROMPT— ant-only system prompt dumpCHICAGO_MCP— computer-use MCP serverDAEMON— daemon worker and supervisorBRIDGE_MODE— remote control bridgeBG_SESSIONS— background session managementTEMPLATES— template job commandsBYOC_ENVIRONMENT_RUNNER— BYOC headless runnerSELF_HOSTED_RUNNER— self-hosted runnerABLATION_BASELINE— harness-science baseline
Dependencies
bun:bundle(feature)../utils/startupProfiler.js../utils/config.js(enableConfigs)- Various fast-path modules loaded dynamically
main.tsx — Full CLI Entry Point
Purpose
The main CLI module. Loaded only after entrypoints/cli.tsx determines no fast-path matches. Defines the Commander.js command tree, handles all CLI flags, orchestrates startup (migrations, trust dialog, MCP config, tool loading), and launches either the interactive REPL or the headless/print (-p) path.
Exported Functions
export async function main(): Promise<void>
export function startDeferredPrefetches(): void
main()
The primary entry point for the full CLI. Responsibilities in order:
- Security: Sets
process.env.NoDefaultCurrentDirectoryInExePath = '1'(Windows PATH attack prevention) - Warning handler:
initializeWarningHandler() - Signal handlers: SIGINT (skip in print mode), exit cursor reset
- Early arg processing:
cc:///cc+unix://URL rewriting (DIRECT_CONNECT feature)--handle-urideep link handling (LODESTONE feature)claude assistant [sessionId]rewriting (KAIROS feature)claude ssh <host>rewriting (SSH_REMOTE feature)
- Settings flag parsing:
eagerLoadSettings()runs beforeinit() - Commander.js setup: Defines the complete command tree (see CLI flags below)
init()call: Validates configs, sets up network, telemetry loading promise- Migration run:
runMigrations()at versionCURRENT_MIGRATION_VERSION = 11 - Trust check: Shows trust dialog if not previously accepted
- Telemetry init:
initializeTelemetryAfterTrust() - Session setup: model, permissions, MCP servers, tools, agents
- Launch: Either
showSetupScreens()+launchRepl(), or headlessrunHeadless()
startDeferredPrefetches()
Called after first REPL render to avoid blocking the initial paint. Skipped when:
CLAUDE_CODE_EXIT_AFTER_FIRST_RENDER=1--baremode (isBareMode())
Prefetches (fire-and-forget):
initUser()getUserContext()prefetchSystemContextIfSafe()getRelevantTips()- AWS/GCP credentials (if Bedrock/Vertex enabled)
countFilesRoundedRg()(3 second timeout)initializeAnalyticsGates()prefetchOfficialMcpUrls()refreshModelCapabilities()settingsChangeDetector.initialize()skillChangeDetector.initialize()(non-bare only)
Constants
| Constant | Value | Purpose |
|---|---|---|
CURRENT_MIGRATION_VERSION |
11 |
Version gate for running config migrations |
CLI Flags (Commander.js)
| Flag | Type | Description |
|---|---|---|
-p, --print <prompt> |
string |
Non-interactive/headless mode |
--model <model> |
string |
Model override |
--fallback-model <model> |
string |
Fallback model for retry |
--permission-mode <mode> |
enum | Permission mode for tool execution |
--dangerously-skip-permissions |
boolean |
Bypass all permission checks |
--verbose |
boolean |
Verbose output |
--debug |
boolean |
Debug mode |
--mcp-config <file> |
string |
MCP config file path |
--add-dir <dir> |
string[] |
Additional directories for CLAUDE.md |
--resume [sessionId] |
string? |
Resume previous session |
--bare |
boolean |
Simple/stripped mode (no UI extras) |
| `--settings <path | json>` | string |
--setting-sources <sources> |
string |
Allowed settings sources |
--output-format <format> |
string | Output format for headless mode |
--max-turns <n> |
number |
Max turns in headless mode |
--max-budget-usd <n> |
number |
Cost budget limit |
--task-budget <n> |
number |
API task budget |
--no-streaming |
boolean |
Disable streaming |
--worktree <branch> |
string |
Git worktree mode |
--agent <type> |
string |
Main thread agent type |
--tmux / --tmux=classic |
boolean |
Use tmux |
--plugin-dir <dir> |
string[] |
Session-only plugin directories |
--input-format <format> |
string | Input format |
--sdk-betas <betas> |
string |
Comma-separated SDK beta headers |
--allowedTools <tools> |
string |
Tool allowlist (CLI override) |
--disallowedTools <tools> |
string |
Tool denylist (CLI override) |
Migration Functions
Executed in runMigrations() when globalConfig.migrationVersion !== 11:
| Function | Purpose |
|---|---|
migrateAutoUpdatesToSettings() |
Move auto-update config |
migrateBypassPermissionsAcceptedToSettings() |
Move bypass flag |
migrateEnableAllProjectMcpServersToSettings() |
Move MCP enable setting |
resetProToOpusDefault() |
Reset Pro model to Opus |
migrateSonnet1mToSonnet45() |
Rename model string |
migrateLegacyOpusToCurrent() |
Upgrade legacy Opus |
migrateSonnet45ToSonnet46() |
Rename to Sonnet 4.6 |
migrateOpusToOpus1m() |
Rename to Opus 1m |
migrateReplBridgeEnabledToRemoteControlAtStartup() |
Rename bridge flag |
resetAutoModeOptInForDefaultOffer() |
Reset auto mode opt-in (TRANSCRIPT_CLASSIFIER) |
migrateFennecToOpus() |
ant-only Fennec model migration |
Top-Level Side Effects
profileCheckpoint('main_tsx_entry') // startup profiling
startMdmRawRead() // parallel MDM subprocess (plutil/reg query)
startKeychainPrefetch() // parallel macOS keychain reads
Key Internal Functions
function logManagedSettings(): void
function isBeingDebugged(): boolean
function logSessionTelemetry(): void
function getCertEnvVarTelemetry(): Record<string, boolean>
async function logStartupTelemetry(): Promise<void>
function runMigrations(): void
function prefetchSystemContextIfSafe(): void
function loadSettingsFromFlag(settingsFile: string): void
function loadSettingSourcesFromFlag(settingSourcesArg: string): void
function eagerLoadSettings(): void
function initializeEntrypoint(isNonInteractive: boolean): void
Pending State Types (feature-gated)
type PendingConnect = {
url: string | undefined
authToken: string | undefined
dangerouslySkipPermissions: boolean
}
type PendingAssistantChat = {
sessionId?: string
discover: boolean
}
type PendingSSH = {
host: string | undefined
cwd: string | undefined
permissionMode: string | undefined
dangerouslySkipPermissions: boolean
local: boolean
extraCliArgs: string[]
}
Feature Flags
DIRECT_CONNECT, KAIROS, SSH_REMOTE, LODESTONE, COORDINATOR_MODE, TRANSCRIPT_CLASSIFIER, BREAK_CACHE_COMMAND, HISTORY_SNIP, DAEMON, BG_SESSIONS, TEMPLATES
replLauncher.tsx — REPL UI Launcher
Purpose
Thin async launcher that dynamically imports App and REPL components (avoiding circular dependencies) and renders them into the Ink root. Exists as a separate file so that App and REPL are loaded lazily.
Exports
export async function launchRepl(
root: Root,
appProps: AppWrapperProps,
replProps: REPLProps,
renderAndRun: (root: Root, element: React.ReactNode) => Promise<void>,
): Promise<void>
Types
type AppWrapperProps = {
getFpsMetrics: () => FpsMetrics | undefined
stats?: StatsStore
initialState: AppState
}
Implementation
Dynamically imports ./components/App.js and ./screens/REPL.js, then calls renderAndRun(root, <App {...appProps}><REPL {...replProps} /></App>).
Dependencies
react./context/stats.js(type only)./ink.js(type only)./screens/REPL.js(type only)./state/AppStateStore.js(type only)./utils/fpsTracker.js(type only)
entrypoints/init.ts — Initialization & Telemetry
Purpose
Handles all early initialization tasks that must complete before the CLI can safely make API calls or show a UI. Memoized so it runs exactly once per process. Telemetry initialization is deferred until after trust is established.
Exports
export const init: () => Promise<void> // memoized with lodash-es/memoize
export function initializeTelemetryAfterTrust(): void
init() — Memoized Async Initialization
Runs once. Sequence:
enableConfigs()— validate and activate config systemapplySafeConfigEnvironmentVariables()— apply env vars that are safe before trustapplyExtraCACertsFromConfig()— injectNODE_EXTRA_CA_CERTSbefore first TLS connectionsetupGracefulShutdown()— register flush/cleanup on exitinitialize1PEventLogging()+ GrowthBook refresh listener (deferred)populateOAuthAccountInfoIfNeeded()(fire-and-forget)initJetBrainsDetection()(fire-and-forget)detectCurrentRepository()(fire-and-forget)initializeRemoteManagedSettingsLoadingPromise()(if eligible)initializePolicyLimitsLoadingPromise()(if eligible)recordFirstStartTime()configureGlobalMTLS()configureGlobalAgents()(proxy)preconnectAnthropicApi()— overlap TCP+TLS with action handler work- Upstream proxy initialization (CLAUDE_CODE_REMOTE only)
setShellIfWindows()— configure git-bash on WindowsregisterCleanup(shutdownLspServerManager)registerCleanup(cleanupSessionTeams)(lazy import)ensureScratchpadDir()(if scratchpad enabled)
Error handling: ConfigParseError → shows InvalidConfigDialog (interactive) or stderr (non-interactive). Other errors re-throw.
initializeTelemetryAfterTrust()
Called once after trust is established. For remote-settings-eligible users: waits for settings to load, then calls applyConfigEnvironmentVariables() before initializing. For SDK/headless with beta tracing: initializes eagerly first.
Internal: doInitializeTelemetry() → setMeterState() → initializeTelemetry() (lazy-loaded OpenTelemetry, ~400KB)
AttributedCounter factory: wraps OpenTelemetry Counter to always merge getTelemetryAttributes() with any additional attributes on each add() call.
Dependencies
../bootstrap/state.js../utils/config.js../services/lsp/manager.js../services/oauth/client.js../services/policyLimits/index.js../services/remoteManagedSettings/index.js../utils/apiPreconnect.js../utils/caCertsConfig.js../utils/cleanupRegistry.js../utils/gracefulShutdown.js../utils/managedEnv.js../utils/mtls.js../utils/proxy.js../utils/telemetry/betaSessionTracing.js../utils/telemetryAttributes.js../utils/windowsPaths.js
entrypoints/mcp.ts — MCP Server Entrypoint
Purpose
Starts Claude Code as an MCP (Model Context Protocol) server, exposing Claude's built-in tools over the stdio transport. Server name: claude/tengu.
Exports
export async function startMCPServer(
cwd: string,
debug: boolean,
verbose: boolean,
): Promise<void>
Implementation Details
- Transport:
StdioServerTransport(stdin/stdout) - Capabilities:
{ tools: {} } - File state cache: LRU with limit 100 files / 25 MB
- Commands exposed: Only
reviewcommand (viaMCP_COMMANDS) - Tool exposure: All tools from
getTools(toolPermissionContext)with empty permission context
ListTools Handler
For each tool:
- Calls
tool.prompt(...)to get the description - Converts
tool.inputSchemato JSON Schema viazodToJsonSchema() - Converts
tool.outputSchema(if present) to JSON Schema — only included if root type isobject(notanyOf/oneOf)
CallTool Handler
- Gets tools via
getTools(emptyPermissionContext) - Finds tool by name; throws if not found
- Calls
tool.isEnabled(),tool.validateInput(), thentool.call() - Builds a
ToolUseContextwith:isNonInteractiveSession: truethinkingConfig: { type: 'disabled' }mcpClients: []
- Returns
{ content: [{ type: 'text', text: result }] }or{ isError: true, content: [...] }
Constants
| Constant | Value |
|---|---|
READ_FILE_STATE_CACHE_SIZE |
100 |
| MCP server name | 'claude/tengu' |
| MCP server version | MACRO.VERSION |
entrypoints/agentSdkTypes.ts — Agent SDK Public API
Purpose
The main entrypoint for Claude Code Agent SDK types. Re-exports all public SDK types and declares stub functions that throw 'not implemented' — actual implementations are provided by the real SDK runtime (the CLI process). This file is the type-only interface for SDK consumers.
Exports
// Control protocol (alpha)
export type { SDKControlRequest, SDKControlResponse } from './sdk/controlTypes.js'
// Core types (common serializable)
export * from './sdk/coreTypes.js'
// Runtime types (callbacks, interfaces)
export * from './sdk/runtimeTypes.js'
// Settings types
export type { Settings } from './sdk/settingsTypes.generated.js'
// Tool types
export * from './sdk/toolTypes.js'
Exported Functions (stubs)
export function tool<Schema extends AnyZodRawShape>(
_name: string,
_description: string,
_inputSchema: Schema,
_handler: (args: InferShape<Schema>, extra: unknown) => Promise<CallToolResult>,
_extras?: { annotations?: ToolAnnotations; searchHint?: string; alwaysLoad?: boolean },
): SdkMcpToolDefinition<Schema>
export function createSdkMcpServer(
_options: CreateSdkMcpServerOptions,
): McpSdkServerConfigWithInstance
export class AbortError extends Error {}
// V1 API
export function query(_params: {
prompt: string | AsyncIterable<SDKUserMessage>
options?: InternalOptions | Options
}): InternalQuery | Query
// V2 API (alpha/unstable)
export function unstable_v2_createSession(_options: SDKSessionOptions): SDKSession
export function unstable_v2_resumeSession(_sessionId: string, _options: SDKSessionOptions): SDKSession
export async function unstable_v2_prompt(_message: string, _options: SDKSessionOptions): Promise<SDKResultMessage>
// Session management
export async function getSessionMessages(_sessionId: string, _options?: GetSessionMessagesOptions): Promise<SessionMessage[]>
export async function listSessions(_options?: ListSessionsOptions): Promise<SDKSessionInfo[]>
export async function getSessionInfo(_sessionId: string, _options?: GetSessionInfoOptions): Promise<SDKSessionInfo | undefined>
export async function renameSession(_sessionId: string, _title: string, _options?: SessionMutationOptions): Promise<void>
export async function tagSession(_sessionId: string, _tag: string | null, _options?: SessionMutationOptions): Promise<void>
Re-exported Constants
export const HOOK_EVENTS = [
'PreToolUse', 'PostToolUse', 'PostToolUseFailure', 'Notification',
'UserPromptSubmit', 'SessionStart', 'SessionEnd', 'Stop', 'StopFailure',
'SubagentStart', 'SubagentStop', 'PreCompact', 'PostCompact',
'PermissionRequest', 'PermissionDenied', 'Setup', 'TeammateIdle',
'TaskCreated', 'TaskCompleted', 'Elicitation', 'ElicitationResult',
'ConfigChange', 'WorktreeCreate', 'WorktreeRemove', 'InstructionsLoaded',
'CwdChanged', 'FileChanged',
] as const
export const EXIT_REASONS = [
'clear', 'resume', 'logout', 'prompt_input_exit', 'other',
'bypass_permissions_disabled',
] as const
entrypoints/sandboxTypes.ts — Sandbox Configuration Types
Purpose
Single source of truth for sandbox configuration types. Both the SDK and settings validation import from here.
Exports
Schemas (Zod, via lazySchema)
export const SandboxNetworkConfigSchema // optional object
export const SandboxFilesystemConfigSchema // optional object
export const SandboxSettingsSchema // passthrough object
Inferred TypeScript Types
export type SandboxSettings // from SandboxSettingsSchema
export type SandboxNetworkConfig // NonNullable<...>
export type SandboxFilesystemConfig // NonNullable<...>
export type SandboxIgnoreViolations // NonNullable<SandboxSettings['ignoreViolations']>
Schema Detail
SandboxNetworkConfigSchema
| Field | Type | Description |
|---|---|---|
allowedDomains |
string[]? |
Allowed outbound domains |
allowManagedDomainsOnly |
boolean? |
Managed-settings-only domain enforcement |
allowUnixSockets |
string[]? |
macOS-only unix socket paths |
allowAllUnixSockets |
boolean? |
Disable unix socket blocking |
allowLocalBinding |
boolean? |
Allow local port binding |
httpProxyPort |
number? |
HTTP proxy port |
socksProxyPort |
number? |
SOCKS proxy port |
SandboxFilesystemConfigSchema
| Field | Type | Description |
|---|---|---|
allowWrite |
string[]? |
Additional write-allowed paths |
denyWrite |
string[]? |
Additional write-denied paths |
denyRead |
string[]? |
Additional read-denied paths |
allowRead |
string[]? |
Paths to re-allow within denyRead regions |
allowManagedReadPathsOnly |
boolean? |
Managed-settings-only read path enforcement |
SandboxSettingsSchema
| Field | Type | Description |
|---|---|---|
enabled |
boolean? |
Enable sandboxing |
failIfUnavailable |
boolean? |
Hard-fail if sandbox unavailable |
autoAllowBashIfSandboxed |
boolean? |
Auto-allow Bash when sandboxed |
allowUnsandboxedCommands |
boolean? |
Allow dangerouslyDisableSandbox param |
network |
SandboxNetworkConfig? |
Network config |
filesystem |
SandboxFilesystemConfig? |
Filesystem config |
ignoreViolations |
Record<string, string[]>? |
Per-rule violation ignoring |
enableWeakerNestedSandbox |
boolean? |
Weaker nested sandbox |
enableWeakerNetworkIsolation |
boolean? |
macOS: allow com.apple.trustd.agent |
excludedCommands |
string[]? |
Commands to exclude from sandboxing |
ripgrep |
{ command: string; args?: string[] }? |
Custom ripgrep config |
Note: Schema uses .passthrough() — undocumented enabledPlatforms field is accepted.
entrypoints/sdk/coreSchemas.ts — SDK Core Zod Schemas
Purpose
Single source of truth for SDK data type schemas. TypeScript types are code-generated from these schemas. Uses lazySchema() wrapper for all schemas (deferred evaluation).
Schema Groups
Usage & Model
export const ModelUsageSchema // inputTokens, outputTokens, cacheRead/Write, webSearch, costUSD, contextWindow, maxOutputTokens
Output Format
export const OutputFormatTypeSchema // z.literal('json_schema')
export const BaseOutputFormatSchema // { type: OutputFormatTypeSchema }
export const JsonSchemaOutputFormatSchema // { type: 'json_schema', schema: Record<string, unknown> }
export const OutputFormatSchema // = JsonSchemaOutputFormatSchema
Config
export const ApiKeySourceSchema // 'user' | 'project' | 'org' | 'temporary' | 'oauth'
export const ConfigScopeSchema // 'local' | 'user' | 'project'
export const SdkBetaSchema // z.literal('context-1m-2025-08-07')
Thinking Config
export const ThinkingAdaptiveSchema // { type: 'adaptive' } — Claude decides (Opus 4.6+)
export const ThinkingEnabledSchema // { type: 'enabled', budgetTokens?: number } — fixed budget
export const ThinkingDisabledSchema // { type: 'disabled' }
export const ThinkingConfigSchema // union of above three
MCP Server Config
export const McpStdioServerConfigSchema // { type?: 'stdio', command, args?, env? }
export const McpSSEServerConfigSchema // { type: 'sse', url, headers? }
export const McpHttpServerConfigSchema // { type: 'http', url, headers? }
export const McpSdkServerConfigSchema // { type: 'sdk', name }
export const McpServerConfigForProcessTransportSchema // union of stdio|sse|http|sdk
export const McpClaudeAIProxyServerConfigSchema // { type: 'claudeai-proxy', url, id }
export const McpServerStatusConfigSchema // union of process transport + claudeai-proxy
export const McpSetServersResultSchema // { added, removed, errors }
MCP Server Status
export const McpServerStatusSchema // { name, status, serverInfo?, error?, config?, scope?, tools?, capabilities? }
// status enum: 'connected' | 'failed' | 'needs-auth' | 'pending' | 'disabled'
Permission Types
export const PermissionUpdateDestinationSchema // 'userSettings' | 'projectSettings' | 'localSettings' | 'session' | 'cliArg'
export const PermissionBehaviorSchema // 'allow' | 'deny' | 'ask'
export const PermissionRuleValueSchema // { toolName, ruleContent? }
export const PermissionUpdateSchema // discriminated union: addRules | replaceRules | removeRules | setMode | addDirectories | removeDirectories
export const PermissionDecisionClassificationSchema // 'user_temporary' | 'user_permanent' | 'user_reject'
export const PermissionResultSchema // { behavior: 'allow', updatedInput?, ... } | { behavior: 'deny', message, ... }
export const PermissionModeSchema // 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | 'dontAsk'
Hook Types
export const HOOK_EVENTS // const array of 28 event names (same as coreTypes.ts)
export const HookEventSchema // z.enum(HOOK_EVENTS)
export const BaseHookInputSchema // { session_id, transcript_path, cwd, permission_mode?, agent_id? }
export const HookInputSchema // full hook input (extends base)
Message Types
All SDKMessage* schemas define the full message taxonomy emitted by query():
| Schema | Description |
|---|---|
SDKMessageSchema |
Root union of all message types |
SDKUserMessageSchema |
User turn message |
SDKStreamlinedTextMessageSchema |
Optimized text-only message |
SDKStreamlinedToolUseSummaryMessageSchema |
Tool use summary (optimized) |
SDKPostTurnSummaryMessageSchema |
End-of-turn summary |
entrypoints/sdk/coreTypes.ts — SDK Core TypeScript Types
Purpose
TypeScript type declarations for the SDK, code-generated from coreSchemas.ts. Not edited manually.
Exports
// Re-exports sandbox types
export type { SandboxFilesystemConfig, SandboxIgnoreViolations, SandboxNetworkConfig, SandboxSettings }
from '../sandboxTypes.js'
// All generated types
export * from './coreTypes.generated.js'
// Utility types not expressible as Zod schemas
export type { NonNullableUsage } from './sdkUtilityTypes.js'
Const Arrays (runtime)
export const HOOK_EVENTS = [...] as const // 28 hook event names
export const EXIT_REASONS = ['clear', 'resume', 'logout', 'prompt_input_exit', 'other', 'bypass_permissions_disabled'] as const
entrypoints/sdk/controlSchemas.ts — SDK Control Protocol Schemas
Purpose
Zod schemas for the SDK control protocol — the bidirectional communication channel between SDK implementations (Python SDK, desktop apps) and the CLI process. Uses SDKControlRequest / SDKControlResponse message wrappers over stdin/stdout.
Control Request Schemas
Each request has a subtype discriminator:
| Schema | subtype |
Description |
|---|---|---|
SDKControlInitializeRequestSchema |
'initialize' |
Initialize SDK session (hooks, MCP, agents, jsonSchema) |
SDKControlInterruptRequestSchema |
'interrupt' |
Interrupt current turn |
SDKControlPermissionRequestSchema |
'can_use_tool' |
Request tool permission |
SDKControlSetPermissionModeRequestSchema |
'set_permission_mode' |
Change permission mode |
SDKControlSetModelRequestSchema |
'set_model' |
Change active model |
SDKControlSetMaxThinkingTokensRequestSchema |
'set_max_thinking_tokens' |
Set thinking budget |
SDKControlMcpStatusRequestSchema |
'mcp_status' |
Get MCP server status |
SDKControlGetContextUsageRequestSchema |
'get_context_usage' |
Get context window breakdown |
SDKControlRewindFilesRequestSchema |
'rewind_files' |
Rewind file changes since a user message |
SDKControlCancelAsyncMessageRequestSchema |
'cancel_async_message' |
Drop queued async message |
SDKControlSeedReadStateRequestSchema |
'seed_read_state' |
Seed readFileState cache |
SDKHookCallbackRequestSchema |
'hook_callback' |
Deliver hook callback |
SDKControlMcpMessageRequestSchema |
'mcp_message' |
Send JSON-RPC to MCP server |
SDKControlMcpSetServersRequestSchema |
'mcp_set_servers' |
Replace dynamic MCP servers |
SDKControlReloadPluginsRequestSchema |
'reload_plugins' |
Reload plugins from disk |
SDKControlMcpReconnectRequestSchema |
'mcp_reconnect' |
Reconnect failed MCP server |
SDKControlMcpToggleRequestSchema |
'mcp_toggle' |
Enable/disable MCP server |
SDKControlStopTaskRequestSchema |
'stop_task' |
Stop a running task |
SDKControlApplyFlagSettingsRequestSchema |
'apply_flag_settings' |
Merge flag settings layer |
SDKControlGetSettingsRequestSchema |
'get_settings' |
Get effective + per-source settings |
SDKControlElicitationRequestSchema |
'elicitation' |
MCP elicitation request |
Control Response Schemas
export const SDKControlInitializeResponseSchema // commands, agents, output_style, models, account, pid?, fast_mode_state?
export const SDKControlMcpStatusResponseSchema // { mcpServers: McpServerStatus[] }
export const SDKControlGetContextUsageResponseSchema // detailed context breakdown
export const SDKControlRewindFilesResponseSchema // { canRewind, error?, filesChanged?, insertions?, deletions? }
export const SDKControlCancelAsyncMessageResponseSchema // { cancelled: boolean }
export const SDKControlMcpSetServersResponseSchema // { added, removed, errors }
export const SDKControlReloadPluginsResponseSchema // { commands, agents, plugins, mcpServers, error_count }
export const SDKControlGetSettingsResponseSchema // { effective, sources, applied? }
export const SDKControlElicitationResponseSchema // { action: 'accept'|'decline'|'cancel', content? }
Wire Message Wrappers
// Outer request envelope
export const SDKControlRequestSchema = z.object({
type: z.literal('control_request'),
request_id: z.string(),
request: SDKControlRequestInnerSchema(), // union of all request types
})
// Response envelope
export const SDKControlResponseSchema = z.object({
type: z.literal('control_response'),
response: z.union([ControlResponseSchema(), ControlErrorResponseSchema()]),
})
// Cancel (for long-running requests)
export const SDKControlCancelRequestSchema = z.object({
type: z.literal('control_cancel_request'),
request_id: z.string(),
})
Aggregate Message Types
// Messages written to stdout by CLI
export const StdoutMessageSchema = z.union([
SDKMessageSchema(),
SDKStreamlinedTextMessageSchema(),
SDKStreamlinedToolUseSummaryMessageSchema(),
SDKPostTurnSummaryMessageSchema(),
SDKControlResponseSchema(),
SDKControlRequestSchema(),
SDKControlCancelRequestSchema(),
SDKKeepAliveMessageSchema(),
])
// Messages read from stdin by CLI
export const StdinMessageSchema = z.union([
SDKUserMessageSchema(),
SDKControlRequestSchema(),
SDKControlResponseSchema(),
SDKKeepAliveMessageSchema(),
SDKUpdateEnvironmentVariablesMessageSchema(),
])
Hook Callback Matcher
export const SDKHookCallbackMatcherSchema = z.object({
matcher: z.string().optional(),
hookCallbackIds: z.array(z.string()),
timeout: z.number().optional(),
})
Context Usage Response Detail
The SDKControlGetContextUsageResponseSchema response includes:
| Field | Type |
|---|---|
categories |
Array<{ name, tokens, color, isDeferred? }> |
totalTokens |
number |
maxTokens |
number |
rawMaxTokens |
number |
percentage |
number |
gridRows |
Array<Array<ContextGridSquare>> |
model |
string |
memoryFiles |
Array<{ path, type, tokens }> |
mcpTools |
Array<{ name, serverName, tokens, isLoaded? }> |
deferredBuiltinTools |
Array<{ name, tokens, isLoaded }>? |
systemTools |
Array<{ name, tokens }>? |
systemPromptSections |
Array<{ name, tokens }>? |
agents |
Array<{ agentType, source, tokens }> |
slashCommands |
{ totalCommands, includedCommands, tokens }? |
skills |
{ totalSkills, includedSkills, tokens, skillFrontmatter[] }? |
autoCompactThreshold |
number? |
isAutoCompactEnabled |
boolean |
messageBreakdown |
detailed message token breakdown ? |
apiUsage |
{ input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens } or null |
query.ts — Core Async Query Loop
Purpose
The core agentic query loop. Drives the back-and-forth between the user's prompt, the Claude API, and tool execution. Implemented as an AsyncGenerator that yields StreamEvent | RequestStartEvent | Message | TombstoneMessage | ToolUseSummaryMessage and returns a Terminal value.
Exports
export type QueryParams = {
messages: Message[]
systemPrompt: SystemPrompt
userContext: { [k: string]: string }
systemContext: { [k: string]: string }
canUseTool: CanUseToolFn
toolUseContext: ToolUseContext
fallbackModel?: string
querySource: QuerySource
maxOutputTokensOverride?: number
maxTurns?: number
skipCacheWrite?: boolean
taskBudget?: { total: number }
deps?: QueryDeps
}
export async function* query(
params: QueryParams,
): AsyncGenerator<
StreamEvent | RequestStartEvent | Message | TombstoneMessage | ToolUseSummaryMessage,
Terminal
>
Internal State Type
type State = {
messages: Message[]
toolUseContext: ToolUseContext
autoCompactTracking: AutoCompactTrackingState | undefined
maxOutputTokensRecoveryCount: number
hasAttemptedReactiveCompact: boolean
maxOutputTokensOverride: number | undefined
pendingToolUseSummary: Promise<ToolUseSummaryMessage | null> | undefined
stopHookActive: boolean | undefined
turnCount: number
transition: Continue | undefined // Why the previous iteration continued
}
Constants
| Constant | Value | Description |
|---|---|---|
MAX_OUTPUT_TOKENS_RECOVERY_LIMIT |
3 |
Max recovery retries for max_output_tokens errors |
Query Loop Architecture
query(params)
└── queryLoop(params, consumedCommandUuids)
├── snapshot config (buildQueryConfig())
├── start memory prefetch (startRelevantMemoryPrefetch)
└── while (true):
1. yield { type: 'stream_request_start' }
2. build queryTracking (chainId/depth)
3. get messages after compact boundary
4. apply tool result budget (applyToolResultBudget)
5. snip compact if needed (HISTORY_SNIP feature)
6. microcompact (deps.microcompact)
7. context collapse (CONTEXT_COLLAPSE feature)
8. build fullSystemPrompt
9. autocompact (deps.autocompact) → maybe yield compact boundary messages
10. check blocking token limit (if not compacted and not reactive compact)
11. call model (deps.callModel) → stream assistant messages
12. execute tools (runTools or StreamingToolExecutor)
13. yield messages, tool results
14. handleStopHooks
15. check for continuation conditions:
- stop hooks blocked → continue with blocking errors
- maxTurns exceeded → return 'max_turns'
- no tool use → check tokenBudget → return 'end_turn'
- tool use → continue loop
Recovery Paths
The query loop includes several error recovery paths:
| Error | Recovery |
|---|---|
max_output_tokens |
Retry up to MAX_OUTPUT_TOKENS_RECOVERY_LIMIT times, incrementing budget |
| Prompt too long | Reactive compact (REACTIVE_COMPACT feature) or return blocking_limit |
| Streaming fallback | Tombstone orphaned messages, create fresh StreamingToolExecutor |
| FallbackTriggeredError | Switch to fallback model, retry |
| Context collapse overflow | Drain staged collapses via CONTEXT_COLLAPSE feature |
Feature Flags
HISTORY_SNIP, CONTEXT_COLLAPSE, REACTIVE_COMPACT, CACHED_MICROCOMPACT, TOKEN_BUDGET, BG_SESSIONS
Tool Execution Integration
- Streaming tool execution (gated on
config.gates.streamingToolExecution): UsesStreamingToolExecutorclass - Sequential tool execution: Uses
runTools()fromservices/tools/toolOrchestration.js - Tool results are yielded back into the loop as
UserMessageobjects
Key Behaviors
persistReplacements: Tool result content replacement is persisted foragent:*andrepl_main_thread*query sourcesbackfillObservableInput: Adds observable fields to tool input before yielding (e.g., expanded file paths) — only when NEW fields are added, not overwrites- Tombstoning: Orphaned messages from failed streaming fallback are tombstoned via
{ type: 'tombstone', message }events - Query chain tracking: Each iteration increments
queryTracking.depth; first iteration creates a newchainIdUUID
QueryEngine.ts — Stateful Query Engine (SDK/Headless)
Purpose
Owns the complete query lifecycle and session state for a conversation. Designed for the SDK/headless (-p) path. One instance per conversation; each submitMessage() call starts a new turn while preserving all state (messages, file cache, usage, permission denials).
Exports
export type QueryEngineConfig = {
cwd: string
tools: Tools
commands: Command[]
mcpClients: MCPServerConnection[]
agents: AgentDefinition[]
canUseTool: CanUseToolFn
getAppState: () => AppState
setAppState: (f: (prev: AppState) => AppState) => void
initialMessages?: Message[]
readFileCache: FileStateCache
customSystemPrompt?: string
appendSystemPrompt?: string
userSpecifiedModel?: string
fallbackModel?: string
thinkingConfig?: ThinkingConfig
maxTurns?: number
maxBudgetUsd?: number
taskBudget?: { total: number }
jsonSchema?: Record<string, unknown>
verbose?: boolean
replayUserMessages?: boolean
handleElicitation?: ToolUseContext['handleElicitation']
includePartialMessages?: boolean
setSDKStatus?: (status: SDKStatus) => void
abortController?: AbortController
orphanedPermission?: OrphanedPermission
snipReplay?: (yieldedSystemMsg: Message, store: Message[]) => { messages: Message[]; executed: boolean } | undefined
}
export class QueryEngine {
constructor(config: QueryEngineConfig)
async *submitMessage(
prompt: string | ContentBlockParam[],
options?: { uuid?: string; isMeta?: boolean },
): AsyncGenerator<SDKMessage, void, unknown>
abort(): void
getMessages(): Message[]
getTotalUsage(): NonNullableUsage
getPermissionDenials(): SDKPermissionDenial[]
}
Internal State
private config: QueryEngineConfig
private mutableMessages: Message[]
private abortController: AbortController
private permissionDenials: SDKPermissionDenial[]
private totalUsage: NonNullableUsage
private hasHandledOrphanedPermission: boolean
private readFileState: FileStateCache
private discoveredSkillNames: Set<string> // cleared each submitMessage()
private loadedNestedMemoryPaths: Set<string> // grows across turns
submitMessage() Flow
- Clear
discoveredSkillNames - Set CWD via
setCwd(cwd) - Wrap
canUseToolto track permission denials - Resolve initial model and thinking config
fetchSystemPromptParts()— build system prompt, user context, system context- Build
systemPromptfromcustomSystemPrompt | defaultSystemPrompt+memoryMechanicsPrompt?+appendSystemPrompt? - Register structured output enforcement (if
jsonSchema+ synthetic output tool) - Build initial
processUserInputContext - Handle orphaned permission (once per lifetime)
- Call
processUserInput()for the user's prompt - Push new messages to
mutableMessages - Persist to transcript (before API call — ensures
--resumeworks even if killed mid-flight) - Replay user messages if
replayUserMessages: true - Update
ToolPermissionContext.alwaysAllowRules.commandfromprocessUserInputresult - Re-build
processUserInputContextwith updated messages and model - Stream from
query()generator — convert toSDKMessage, yield - Track usage via
accumulateUsage()/updateUsage() - Handle local command output, compact boundaries, snip boundaries
Transcript Persistence
In submitMessage(), the user's messages are written to the transcript before entering the API query loop. Timing variants:
--bare/isBareMode(): Fire-and-forget (saves ~4ms on SSD)CLAUDE_CODE_EAGER_FLUSHorCLAUDE_CODE_IS_COWORK: Awaited + flushed- Default: Awaited
ProcessUserInputContext Internals
First build (before slash command processing):
setMessages: writes back tomutableMessagesisNonInteractiveSession: true
Second build (after slash command processing):
setMessages: no-op (slash commands already committed)
query/config.ts — Query Configuration Snapshot
Purpose
Captures immutable configuration values at query entry. Separated from per-iteration state to make future step() extraction (pure reducer pattern) tractable. Intentionally excludes feature() gates (those are build-time tree-shaking boundaries).
Exports
export type QueryConfig = {
sessionId: SessionId
gates: {
streamingToolExecution: boolean // Statsig: 'tengu_streaming_tool_execution2'
emitToolUseSummaries: boolean // env: CLAUDE_CODE_EMIT_TOOL_USE_SUMMARIES
isAnt: boolean // env: USER_TYPE === 'ant'
fastModeEnabled: boolean // env: !CLAUDE_CODE_DISABLE_FAST_MODE
}
}
export function buildQueryConfig(): QueryConfig
Gate Details
| Gate | Source | Key |
|---|---|---|
streamingToolExecution |
Statsig (cached, may be stale) | tengu_streaming_tool_execution2 |
emitToolUseSummaries |
Environment variable | CLAUDE_CODE_EMIT_TOOL_USE_SUMMARIES |
isAnt |
Environment variable | USER_TYPE === 'ant' |
fastModeEnabled |
Environment variable | !CLAUDE_CODE_DISABLE_FAST_MODE |
query/deps.ts — Query Dependency Injection
Purpose
I/O dependencies for query(), passed via QueryParams.deps. Enables test injection of fakes without spyOn-per-module boilerplate.
Exports
export type QueryDeps = {
callModel: typeof queryModelWithStreaming // API streaming call
microcompact: typeof microcompactMessages // microcompaction
autocompact: typeof autoCompactIfNeeded // autocompaction
uuid: () => string // UUID generation
}
export function productionDeps(): QueryDeps
Production Dependencies
| Dep | Implementation |
|---|---|
callModel |
queryModelWithStreaming from services/api/claude.js |
microcompact |
microcompactMessages from services/compact/microCompact.js |
autocompact |
autoCompactIfNeeded from services/compact/autoCompact.js |
uuid |
randomUUID from Node.js crypto module |
query/stopHooks.ts — Stop Hook Orchestration
Purpose
Orchestrates all end-of-turn hooks: Stop, SubagentStop, TeammateIdle, TaskCompleted. Also handles background side-effects (prompt suggestion, memory extraction, auto-dream, computer-use cleanup).
Exports
export async function* handleStopHooks(
messagesForQuery: Message[],
assistantMessages: AssistantMessage[],
systemPrompt: SystemPrompt,
userContext: { [k: string]: string },
systemContext: { [k: string]: string },
toolUseContext: ToolUseContext,
querySource: QuerySource,
stopHookActive?: boolean,
): AsyncGenerator<
StreamEvent | RequestStartEvent | Message | TombstoneMessage | ToolUseSummaryMessage,
StopHookResult
>
type StopHookResult = {
blockingErrors: Message[]
preventContinuation: boolean
}
Execution Order
saveCacheSafeParams()— snapshot context for prompt suggestion / btw queries (main thread and SDK only)- Template job classification (TEMPLATES feature, main thread only, non-subagent):
classifyAndWriteState()— max 60s timeout - Background side-effects (non-bare mode):
executePromptSuggestion()(fire-and-forget, unlessCLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false)executeExtractMemories()(EXTRACT_MEMORIES feature, main thread only)executeAutoDream()(non-subagent)
- Computer-use cleanup (CHICAGO_MCP feature, main thread only):
cleanupComputerUseAfterTurn() executeStopHooks()— runs Stop/SubagentStop hooks in parallel, yields progress messages- Summary message if any hooks ran
- Notification if hook errors occurred
- Teammate hooks (if
isTeammate()):executeTaskCompletedHooks()for in-progress tasks owned by this agentexecuteTeammateIdleHooks()
Hook Result Types
| Result Field | Effect |
|---|---|
blockingError |
Creates UserMessage with isMeta: true, added to blockingErrors |
preventContinuation |
Sets flag, yields hook_stopped_continuation attachment |
| Abort signal | Yields UserInterruptionMessage, returns { blockingErrors: [], preventContinuation: true } |
Telemetry Events
tengu_pre_stop_hooks_cancelled— hook aborted mid-executiontengu_stop_hook_error— exception in hook execution
query/tokenBudget.ts — Token Budget Tracking
Purpose
Tracks token budget utilization across query loop iterations to decide whether to continue or stop based on the +500k auto-continue feature (distinct from the API task_budget).
Exports
export type BudgetTracker = {
continuationCount: number
lastDeltaTokens: number
lastGlobalTurnTokens: number
startedAt: number
}
export function createBudgetTracker(): BudgetTracker
export type TokenBudgetDecision =
| { action: 'continue'; nudgeMessage: string; continuationCount: number; pct: number; turnTokens: number; budget: number }
| { action: 'stop'; completionEvent: { continuationCount: number; pct: number; turnTokens: number; budget: number; diminishingReturns: boolean; durationMs: number } | null }
export function checkTokenBudget(
tracker: BudgetTracker,
agentId: string | undefined,
budget: number | null,
globalTurnTokens: number,
): TokenBudgetDecision
Constants
| Constant | Value | Description |
|---|---|---|
COMPLETION_THRESHOLD |
0.9 |
90% of budget consumed → stop |
DIMINISHING_THRESHOLD |
500 |
Tokens delta < 500 across two checks → diminishing returns |
Algorithm
checkTokenBudget(tracker, agentId, budget, globalTurnTokens):
if agentId or budget null or budget <= 0:
return { action: 'stop', completionEvent: null }
pct = round(turnTokens / budget * 100)
deltaSinceLast = globalTurnTokens - tracker.lastGlobalTurnTokens
isDiminishing = (
continuationCount >= 3 AND
deltaSinceLast < 500 AND
lastDeltaTokens < 500
)
if !isDiminishing AND turnTokens < budget * 0.9:
→ return { action: 'continue', nudgeMessage: ... }
if isDiminishing OR continuationCount > 0:
→ return { action: 'stop', completionEvent: { ..., diminishingReturns: isDiminishing } }
→ return { action: 'stop', completionEvent: null }
context.ts — System & User Context Providers
Purpose
Provides memoized context functions that build the systemContext and userContext dictionaries prepended to each conversation. Both are cached for the duration of the conversation. Includes the git status, CLAUDE.md content, and current date.
Exports
export function getSystemPromptInjection(): string | null
export function setSystemPromptInjection(value: string | null): void
export const getGitStatus: () => Promise<string | null> // memoized
export const getSystemContext: () => Promise<{ [k: string]: string }> // memoized
export const getUserContext: () => Promise<{ [k: string]: string }> // memoized
Constants
| Constant | Value | Description |
|---|---|---|
MAX_STATUS_CHARS |
2000 |
Max characters for git status --short output |
getGitStatus() — Memoized
Returns null in test environment. Otherwise:
- Checks
getIsGit()— returnsnullif not a git repo - Runs in parallel:
getBranch(),getDefaultBranch(),git status --short,git log --oneline -n 5,git config user.name - Truncates status at
MAX_STATUS_CHARS(appends truncation notice) - Returns formatted string with branch, main branch, git user, status, and recent commits
Format:
This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation.
Current branch: <branch>
Main branch (you will usually use this for PRs): <mainBranch>
Git user: <userName>
Status:
<status or "(clean)">
Recent commits:
<log>
getSystemContext() — Memoized
{
gitStatus?: string, // from getGitStatus() — skipped for CCR or when git disabled
cacheBreaker?: string, // "[CACHE_BREAKER: injection]" — BREAK_CACHE_COMMAND feature only
}
Skips git status when:
CLAUDE_CODE_REMOTE=true(CCR environment)shouldIncludeGitInstructions()returns false
getUserContext() — Memoized
{
claudeMd?: string, // Combined CLAUDE.md content
currentDate: string, // "Today's date is YYYY-MM-DD."
}
CLAUDE.md loading is disabled when:
CLAUDE_CODE_DISABLE_CLAUDE_MDS=trueisBareMode()AND no--add-dirdirectories
Side effect: calls setCachedClaudeMdContent() to cache content for auto-mode classifier.
setSystemPromptInjection() Side Effect
When injection changes, clears both getUserContext.cache and getSystemContext.cache (from lodash memoize) to force rebuild.
history.ts — Prompt History Management
Purpose
Manages the persistent prompt history (up-arrow navigation and Ctrl+R fuzzy search). History is stored as JSONL in ~/.claude/history.jsonl. Handles pasted content with both inline (≤ 1024 bytes) and external hash-based storage. Entries are scoped to project root.
Exports
// Pasted content helpers
export function getPastedTextRefNumLines(text: string): number
export function formatPastedTextRef(id: number, numLines: number): string
export function formatImageRef(id: number): string
export function parseReferences(input: string): Array<{ id: number; match: string; index: number }>
export function expandPastedTextRefs(input: string, pastedContents: Record<number, PastedContent>): string
// History reading
export async function* makeHistoryReader(): AsyncGenerator<HistoryEntry>
export async function* getTimestampedHistory(): AsyncGenerator<TimestampedHistoryEntry>
export async function* getHistory(): AsyncGenerator<HistoryEntry>
// History writing
export function addToHistory(command: HistoryEntry | string): void
export function clearPendingHistoryEntries(): void
export function removeLastFromHistory(): void
// Types
export type TimestampedHistoryEntry = {
display: string
timestamp: number
resolve: () => Promise<HistoryEntry>
}
Constants
| Constant | Value | Description |
|---|---|---|
MAX_HISTORY_ITEMS |
100 |
Max entries returned from getHistory() |
MAX_PASTED_CONTENT_LENGTH |
1024 |
Threshold for inline vs hash-based storage |
Internal Types
type LogEntry = {
display: string
pastedContents: Record<number, StoredPastedContent>
timestamp: number
project: string
sessionId?: string
}
type StoredPastedContent = {
id: number
type: 'text' | 'image'
content?: string // inline (≤ MAX_PASTED_CONTENT_LENGTH)
contentHash?: string // hash ref for large pastes
mediaType?: string
filename?: string
}
History File
Path: join(getClaudeConfigHomeDir(), 'history.jsonl')
Locking: Uses file-based lockfile with:
stale: 10000ms- Retries: 3, min timeout 50ms
addToHistory() Behavior
- Skips if
CLAUDE_CODE_SKIP_PROMPT_HISTORY=true(tmux subprocess sessions) - Registers cleanup hook on first call (flushes pending entries on process exit)
- Calls
addToPromptHistory()async (fire-and-forget)
getHistory() Ordering
Current session entries first (newest-first), then other session entries (also newest-first). Same MAX_HISTORY_ITEMS window. This prevents concurrent sessions from interleaving up-arrow history.
removeLastFromHistory() — Undo Last Entry
Fast path: if entry is still in pendingEntries, splices it out.
Slow path: if already flushed, adds timestamp to skippedTimestamps set (consulted during reads).
One-shot: clears lastAddedEntry after use.
Paste Reference Pattern
Reference format: \[(Pasted text|Image|\.\.\.Truncated text) #(\d+)(?: \+\d+ lines)?(\.)*\]
Examples:
[Pasted text #1]— zero-line paste[Pasted text #1 +10 lines]— multi-line paste (counts\noccurrences)[Image #2]— image paste
cost-tracker.ts — Session Cost Tracking
Purpose
Manages session cost/usage tracking, persistence to project config, and formatted display. Delegates all state to bootstrap/state.ts.
Exports
// Re-exports from bootstrap/state.ts
export { getTotalCostUSD as getTotalCost }
export { getTotalDuration }
export { getTotalAPIDuration }
export { getTotalAPIDurationWithoutRetries }
export { addToTotalLinesChanged }
export { getTotalLinesAdded, getTotalLinesRemoved }
export { getTotalInputTokens, getTotalOutputTokens }
export { getTotalCacheReadInputTokens, getTotalCacheCreationInputTokens }
export { getTotalWebSearchRequests }
export { hasUnknownModelCost }
export { resetStateForTests, resetCostState }
export { setHasUnknownModelCost }
export { getModelUsage, getUsageForModel }
export { formatCost } // internal function, also exported
// New functions
export function getStoredSessionCosts(sessionId: string): StoredCostState | undefined
export function restoreCostStateForSession(sessionId: string): boolean
export function saveCurrentSessionCosts(fpsMetrics?: FpsMetrics): void
export function addToTotalSessionCost(cost: number, usage: Usage, model: string): number
export function formatTotalCost(): string
Internal Types
type StoredCostState = {
totalCostUSD: number
totalAPIDuration: number
totalAPIDurationWithoutRetries: number
totalToolDuration: number
totalLinesAdded: number
totalLinesRemoved: number
lastDuration: number | undefined
modelUsage: { [modelName: string]: ModelUsage } | undefined
}
addToTotalSessionCost() Detail
- Calls
addToTotalModelUsage()— aggregates per-model token counts - Calls
addToTotalCostState()in bootstrap/state.ts - Records to OpenTelemetry counters (
getCostCounter(),getTokenCounter()) - Processes advisor usage from
getAdvisorUsage(usage)— recursively calls itself for each advisor model - Returns total cost (including advisor costs)
saveCurrentSessionCosts() — Persisted Fields
Writes to project config:
lastCost,lastAPIDuration,lastAPIDurationWithoutRetries,lastToolDuration,lastDurationlastLinesAdded,lastLinesRemovedlastTotalInputTokens,lastTotalOutputTokenslastTotalCacheCreationInputTokens,lastTotalCacheReadInputTokenslastTotalWebSearchRequestslastFpsAverage,lastFpsLow1Pct(fromfpsMetrics)lastModelUsage(per-model: input/output/cache tokens, web searches, cost)lastSessionId
formatTotalCost() — Display Format
Total cost: $X.XXXX
Total duration (API): Xs
Total duration (wall): Xs
Total code changes: N lines added, N lines removed
Usage by model:
claude-sonnet-4-6: N input, N output, N cache read, N cache write ($X.XXXX)
formatCost() Helper
function formatCost(cost: number, maxDecimalPlaces: number = 4): string
// Returns "$X.XX" if cost > 0.5, else "$X.XXXX" (or fewer decimal places)
costHook.ts — React Cost Summary Hook
Purpose
React hook that registers a process.exit listener to print cost summary and save session costs when the process exits.
Exports
export function useCostSummary(getFpsMetrics?: () => FpsMetrics | undefined): void
Behavior
- Runs once on mount (empty dependency array)
- On
process.exit:- If
hasConsoleBillingAccess(): writesformatTotalCost()to stdout - Calls
saveCurrentSessionCosts(getFpsMetrics?.())
- If
- Cleans up the exit listener on unmount
projectOnboardingState.ts — Project Onboarding State
Purpose
Tracks and controls the display of the project onboarding checklist (shown when a user first opens a new project).
Exports
export type Step = {
key: string
text: string
isComplete: boolean
isCompletable: boolean
isEnabled: boolean
}
export function getSteps(): Step[]
export function isProjectOnboardingComplete(): boolean
export function maybeMarkProjectOnboardingComplete(): void
export const shouldShowProjectOnboarding: () => boolean // memoized
export function incrementProjectOnboardingSeenCount(): void
Steps
| Key | Condition to enable | Completion check |
|---|---|---|
'workspace' |
isDirEmpty(getCwd()) |
Never auto-completes (user must act) |
'claudemd' |
!isDirEmpty(getCwd()) |
existsSync(join(getCwd(), 'CLAUDE.md')) |
shouldShowProjectOnboarding() — Memoized
Returns false if any of:
projectConfig.hasCompletedProjectOnboarding === trueprojectConfig.projectOnboardingSeenCount >= 4process.env.IS_DEMOis setisProjectOnboardingComplete()returns true
maybeMarkProjectOnboardingComplete() Behavior
Short-circuits on hasCompletedProjectOnboarding: true in cached config (avoids filesystem hit on every prompt submit). Saves to project config when complete.
bootstrap/state.ts — Global Session State
Purpose
The single module-level state singleton for a Claude Code process. DO NOT ADD MORE STATE HERE (documented with triple comment emphasis). Contains all session-scoped values including costs, tokens, model configuration, telemetry, agent state, and session identity.
State Structure
The State type is a large flat object. Key groupings:
Session Identity
| Field | Type | Description |
|---|---|---|
sessionId |
SessionId |
UUID (randomUUID at init) |
parentSessionId |
SessionId? |
Parent session for lineage tracking |
originalCwd |
string |
CWD at startup (NFC-normalized) |
projectRoot |
string |
Stable project root (set by --worktree; not updated mid-session) |
cwd |
string |
Current working directory (updated by setCwd) |
sessionProjectDir |
`string | null` |
Cost & Token Counters
| Field | Type |
|---|---|
totalCostUSD |
number |
totalAPIDuration |
number |
totalAPIDurationWithoutRetries |
number |
totalToolDuration |
number |
totalLinesAdded, totalLinesRemoved |
number |
modelUsage |
{ [modelName: string]: ModelUsage } |
Per-Turn Counters (reset each turn)
| Field | Type |
|---|---|
turnHookDurationMs |
number |
turnToolDurationMs |
number |
turnClassifierDurationMs |
number |
turnToolCount |
number |
turnHookCount |
number |
turnClassifierCount |
number |
Model Configuration
| Field | Type | Description |
|---|---|---|
mainLoopModelOverride |
ModelSetting? |
Set by --model flag |
initialMainLoopModel |
ModelSetting |
Set at startup |
modelStrings |
`ModelStrings | null` |
Telemetry / OpenTelemetry
| Field | Type |
|---|---|
meter |
`Meter |
meterProvider |
`MeterProvider |
loggerProvider |
`LoggerProvider |
tracerProvider |
`BasicTracerProvider |
eventLogger |
`ReturnType |
sessionCounter |
`AttributedCounter |
locCounter, prCounter, commitCounter |
`AttributedCounter |
costCounter, tokenCounter |
`AttributedCounter |
codeEditToolDecisionCounter, activeTimeCounter |
`AttributedCounter |
statsStore |
`{ observe(name, value): void } |
Session Flags
| Field | Default | Description |
|---|---|---|
isInteractive |
false |
Interactive REPL mode |
kairosActive |
false |
Assistant (KAIROS) mode active |
strictToolResultPairing |
false |
HFI mode — throws on mismatch |
sessionBypassPermissionsMode |
false |
Not persisted |
sessionPersistenceDisabled |
false |
Disable transcript write |
sessionTrustAccepted |
false |
Session-only trust (home dir) |
hasExitedPlanMode |
false |
For re-entry guidance |
scheduledTasksEnabled |
false |
Set by cron scheduler |
isRemoteMode |
false |
--remote flag |
Prompt Cache Latches
All start as null (not yet triggered), flip to true once, and stay true:
| Field | Purpose |
|---|---|
afkModeHeaderLatched |
Sticky AFK_MODE_BETA_HEADER |
fastModeHeaderLatched |
Sticky FAST_MODE_BETA_HEADER |
cacheEditingHeaderLatched |
Sticky cache-editing beta header |
thinkingClearLatched |
Clear thinking after >1h idle |
Exported Functions (selected)
Session Identity
export function getSessionId(): SessionId
export function regenerateSessionId(options?: { setCurrentAsParent?: boolean }): SessionId
export function getParentSessionId(): SessionId | undefined
export function switchSession(sessionId: SessionId, projectDir?: string | null): void
export function getSessionProjectDir(): string | null
export const onSessionSwitch: (listener: (id: SessionId) => void) => () => void
export function getOriginalCwd(): string
export function setOriginalCwd(cwd: string): void
export function getProjectRoot(): string
export function setProjectRoot(cwd: string): void
export function getCwdState(): string
export function setCwdState(cwd: string): void
Cost / Duration Tracking
export function addToTotalDurationState(duration: number, durationWithoutRetries: number): void
export function addToTotalCostState(cost: number, modelUsage: ModelUsage, model: string): void
export function getTotalCostUSD(): number
export function getTotalAPIDuration(): number
export function getTotalDuration(): number
export function getTotalAPIDurationWithoutRetries(): number
export function getTotalToolDuration(): number
export function addToToolDuration(duration: number): void
export function getTurnHookDurationMs(): number
export function addToTurnHookDuration(duration: number): void
export function resetTurnHookDuration(): void
export function getTurnHookCount(): number
export function getTurnToolDurationMs(): number
export function resetTurnToolDuration(): void
export function getTurnToolCount(): number
export function getTurnClassifierDurationMs(): number
export function addToTurnClassifierDuration(duration: number): void
export function resetTurnClassifierDuration(): void
export function getTurnClassifierCount(): number
Token Accounting
export function getTotalInputTokens(): number // sumBy modelUsage.inputTokens
export function getTotalOutputTokens(): number
export function getTotalCacheReadInputTokens(): number
export function getTotalCacheCreationInputTokens(): number
export function getTotalWebSearchRequests(): number
export function getModelUsage(): { [modelName: string]: ModelUsage }
export function getUsageForModel(model: string): ModelUsage | undefined
Turn Token Budget
export function getTurnOutputTokens(): number // current turn output tokens
export function getCurrentTurnTokenBudget(): number | null
export function snapshotOutputTokensForTurn(budget: number | null): void
export function getBudgetContinuationCount(): number
export function incrementBudgetContinuationCount(): void
Post-Compaction Tracking
export function markPostCompaction(): void // sets pendingPostCompaction=true
export function consumePostCompaction(): boolean // returns true once after compaction, resets
Cost State Persistence
export function resetCostState(): void
export function setCostStateForRestore({ totalCostUSD, totalAPIDuration, ... }): void
export function resetStateForTests(): void
Scroll Drain
export function markScrollActivity(): void
export function getIsScrollDraining(): boolean
export async function waitForScrollIdle(): Promise<void>
Model
export function getMainLoopModelOverride(): ModelSetting | undefined
export function getInitialMainLoopModel(): ModelSetting
export function setMainLoopModelOverride(model: ModelSetting | undefined): void
export function setInitialMainLoopModel(model: ModelSetting): void
export function getSdkBetas(): string[] | undefined
export function setSdkBetas(betas: string[] | undefined): void
Telemetry Setters
export function setMeter(meter: Meter, createAttributedCounter: ...): void
export function getSessionCounter(): AttributedCounter | null
export function setStatsStore(store: ...): void
export function getStatsStore(): ...
export function updateLastInteractionTime(immediate?: boolean): void
export function flushInteractionTime(): void
Misc Session State
export function setIsInteractive(v: boolean): void
export function getIsNonInteractiveSession(): boolean
export function setKairosActive(v: boolean): void
export function isSessionPersistenceDisabled(): boolean
export function setSessionPersistenceDisabled(v: boolean): void
export function setMainThreadAgentType(type: string | undefined): void
export function setIsRemoteMode(v: boolean): void
export function setClientType(type: string): void
export function setSessionSource(source: string | undefined): void
export function setInlinePlugins(dirs: string[]): void
export function getAdditionalDirectoriesForClaudeMd(): string[]
export function setAdditionalDirectoriesForClaudeMd(dirs: string[]): void
export function setAllowedChannels(channels: ChannelEntry[]): void
export function setAllowedSettingSources(sources: SettingSource[]): void
export function setSdkBetas(betas: string[] | undefined): void
export function setCachedClaudeMdContent(content: string | null): void
export function getLastMainRequestId(): string | undefined
export function setLastMainRequestId(requestId: string): void
export function setTeleportedSessionInfo(info: ...): void
Important Design Notes
- Singleton:
STATEis a module-level constant, initialized once viagetInitialState() projectRootvsoriginalCwd:projectRootis set at startup (including by--worktree) and never updated byEnterWorktreeTool.originalCwdis for file operations;projectRootis for session identity (history, skills)sessionProjectDir: Always reset onswitchSession()andregenerateSessionId().nullmeans "derive fromoriginalCwd"- Scroll drain: Module-level (not in
STATE) — ephemeral hot-path flag with 150ms debounce - Bootstrap isolation: This module must remain a leaf in the import DAG (cannot import from
src/utils/directly — uses path aliases)
AttributedCounter Type
export type AttributedCounter = {
add(value: number, additionalAttributes?: Attributes): void
}
ChannelEntry Type
export type ChannelEntry =
| { kind: 'plugin'; name: string; marketplace: string; dev?: boolean }
| { kind: 'server'; name: string; dev?: boolean }
assistant/sessionHistory.ts — Remote Session History Pagination
Purpose
Fetches conversation event history from the Claude API for remote (CCR/BYOC) sessions. Used by the assistant/teleport feature to replay conversation history when resuming a remote session.
Exports
export const HISTORY_PAGE_SIZE = 100
export type HistoryPage = {
events: SDKMessage[] // chronological within page
firstId: string | null // oldest event ID → before_id cursor for next-older page
hasMore: boolean // true = older events exist
}
export type HistoryAuthCtx = {
baseUrl: string
headers: Record<string, string>
}
export async function createHistoryAuthCtx(sessionId: string): Promise<HistoryAuthCtx>
export async function fetchLatestEvents(ctx: HistoryAuthCtx, limit?: number): Promise<HistoryPage | null>
export async function fetchOlderEvents(ctx: HistoryAuthCtx, beforeId: string, limit?: number): Promise<HistoryPage | null>
API Endpoints
Base URL: ${getOauthConfig().BASE_API_URL}/v1/sessions/${sessionId}/events
| Function | Query Params | Description |
|---|---|---|
fetchLatestEvents |
{ limit, anchor_to_latest: true } |
Newest limit events, chronological |
fetchOlderEvents |
{ limit, before_id: beforeId } |
Events before cursor |
Authentication
Requires OAuth access token + organization UUID. Headers:
- Standard OAuth headers (
getOAuthHeaders(accessToken)) anthropic-beta: ccr-byoc-2025-07-29x-organization-uuid: orgUUID
Request Configuration
- Timeout: 15,000ms per request
- Status validation:
validateStatus: () => true(manual status check) - Error handling: Returns
nullon network error or non-200 status; logs HTTP status to debug
Response Type
type SessionEventsResponse = {
data: SDKMessage[]
has_more: boolean
first_id: string | null
last_id: string | null
}
Cross-Cutting Concerns
Startup Profiling
Throughout these files, profileCheckpoint(label) calls mark timing milestones for startup performance analysis. Key checkpoints:
| Checkpoint | Location |
|---|---|
main_tsx_entry |
First line of main.tsx (before imports) |
main_tsx_imports_loaded |
After all imports loaded |
cli_entry |
cli.tsx after profile import |
cli_before_main_import |
Before import('../main.js') |
cli_after_main_import |
After main.ts loaded |
cli_after_main_complete |
After main() returns |
init_function_start / init_function_end |
init.ts boundaries |
main_function_start |
Entry to main() |
query_fn_entry |
Each query loop iteration |
query_api_streaming_start |
Before first API streaming call |
Feature Flags System
Feature flags (feature('FLAG_NAME')) from bun:bundle are build-time dead-code elimination gates, not runtime toggles. The bun bundler evaluates these at build time and removes unreachable code branches from external builds.
Runtime gates use:
checkStatsigFeatureGate_CACHED_MAY_BE_STALE()— Statsig experiment gatesisEnvTruthy(process.env.*)— environment variable gates- GrowthBook feature values — for more complex multi-variant experiments
Query Source Values
The querySource: QuerySource parameter in query() and handleStopHooks() categorizes the origin of each query:
| Value | Description |
|---|---|
'repl_main_thread' |
Interactive REPL, main user turn |
'sdk' |
SDK/headless path |
'agent:*' |
Subagent (AgentTool) |
'compact' |
Compaction fork |
'session_memory' |
Session memory fork |
Values starting with 'agent:' or 'repl_main_thread' enable transcript persistence of content replacements.
Dependency Injection Pattern
QueryDeps (in query/deps.ts) is the primary example of DI in this codebase. Rather than jest.spyOn across 6-8 files, tests can pass deps to QueryParams directly. The pattern is intentionally narrow (4 deps) with the note that it can be expanded.