01 The Entrypoint Layer
The src/entrypoints/ directory is the boundary between the outside world and Claude Code's internals.
It contains five surface-level files — cli.tsx, init.ts, mcp.ts,
agentSdkTypes.ts, and sandboxTypes.ts — plus an sdk/ sub-directory
that holds the serializable contract types. main.tsx sits one level up and is the
single large Commander-based CLI handler that nearly every interactive and headless path flows through.
cli.tsx is a thin bootstrap that pattern-matches on process.argv and fast-paths
special commands before loading the 200+ module import graph in main.tsx.
This keeps --version and daemon-worker startup near-instant.
02 cli.tsx — The Bootstrap Dispatcher
src/entrypoints/cli.tsx is the actual binary entrypoint. It runs before any other
module evaluation. Its design philosophy: load as little as possible for each fast-path.
Fast Paths (in order of detection)
--version / -v
Zero imports. Prints MACRO.VERSION inlined at build time and exits immediately.
--dump-system-prompt
Internal eval tool. Loads only config + model + prompts modules to render and print the system prompt.
Chrome / Computer-Use MCP
--claude-in-chrome-mcp and --computer-use-mcp launch standalone MCP servers without the full CLI stack.
--daemon-worker=<kind>
Spawned by the daemon supervisor. Loads only the worker registry — no configs, no auth, no telemetry at this layer.
Bridge / Remote Control
remote-control, rc, sync, bridge — connects the local machine as a remote-controlled environment for claude.ai.
daemon subcommand
Long-running supervisor process. Sets up sinks then delegates to daemon/main.js.
Background sessions
ps, logs, attach, kill, --bg — session registry management without loading the interactive UI.
Full CLI (main.tsx)
Everything else. Loads the complete Commander-based CLI handler with all 200+ module imports.
// From cli.tsx — each fast-path is gated by a build-time feature() flag
if (feature('BRIDGE_MODE') && (args[0] === 'remote-control' || args[0] === 'rc'
|| args[0] === 'remote' || args[0] === 'sync' || args[0] === 'bridge')) {
// Auth check → GrowthBook gate → policy limits → bridgeMain()
const { bridgeMain } = await import('../bridge/bridgeMain.js');
await bridgeMain(args.slice(1));
return;
}
feature() flag — a Bun build-time dead-code-elimination gate.
This means unsupported features are completely absent from external distribution builds, not just
gated at runtime.
03 init.ts — Shared Initialization
src/entrypoints/init.ts is a memoized init() function shared by all
non-trivial entrypoints. It is not called for fast-paths. It performs all the
one-time setup that must happen before the first API call.
What init() does (in order)
- enableConfigs() — validates and activates the settings system
- applySafeConfigEnvironmentVariables() — applies safe env vars before the trust dialog
- applyExtraCACertsFromConfig() — sets TLS CA certs before the first TLS connection
- setupGracefulShutdown() — registers SIGTERM/SIGINT handlers for flush-on-exit
- initialize1PEventLogging() — lazily loads OpenTelemetry analytics (deferred ~400KB)
- populateOAuthAccountInfoIfNeeded() — fills missing OAuth cache from keychain
- initJetBrainsDetection() — detects IDE host asynchronously
- initializeRemoteManagedSettingsLoadingPromise() — sets up enterprise policy loading
- configureGlobalMTLS() / configureGlobalAgents() — TLS + proxy agents
- preconnectAnthropicApi() — warms TCP+TLS (~150ms) in parallel with CLI parsing
- initUpstreamProxy() — CCR upstream proxy for org-injected credentials (CLAUDE_CODE_REMOTE)
- registerCleanup(shutdownLspServerManager) — LSP teardown on exit
- ensureScratchpadDir() — creates scratch dir if enabled
A separate initializeTelemetryAfterTrust() function is called only after the
user has accepted the trust dialog. This separates consent-independent setup from consent-gated
telemetry, and waits for remote managed settings to load before initializing OpenTelemetry exporters.
04 mcp.ts — Claude Code as an MCP Server
When invoked with claude --mcp, Claude Code runs as a standard
Model Context Protocol server over stdio. This exposes all of Claude Code's
tools (Bash, Edit, Read, WebFetch, etc.) to other MCP clients — editors, agents, or
automation scripts can call them directly without spawning a full REPL.
// src/entrypoints/mcp.ts
const server = new Server(
{ name: 'claude/tengu', version: MACRO.VERSION },
{ capabilities: { tools: {} } }
)
// ListTools: enumerate every Claude Code tool with its Zod-derived JSON schema
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: await Promise.all(tools.map(async tool => ({
...tool,
description: await tool.prompt(...),
inputSchema: zodToJsonSchema(tool.inputSchema),
})))
}))
// CallTool: validate input, check permissions, execute, return result
server.setRequestHandler(CallToolRequestSchema, async ({ params }) => {
const tool = findToolByName(tools, params.name)
return await tool.call(params.arguments, toolUseContext, ...)
})
isNonInteractiveSession: true and disables
thinking (thinkingConfig: { type: 'disabled' }). It also exposes only the
review slash command since slash commands are not meaningful in this context.
05 agentSdkTypes.ts — The Public SDK Contract
src/entrypoints/agentSdkTypes.ts is the main export of the Agent SDK package.
It re-exports the full public API from three sub-modules and declares the top-level functions
that SDK consumers call. All function bodies throw 'not implemented' in this file —
the actual implementations are injected at runtime by the SDK transport layer.
Module Structure
| Module | Purpose | Examples |
|---|---|---|
sdk/coreTypes.ts |
Serializable, transport-safe types generated from Zod schemas | SDKMessage, SDKUserMessage, ModelUsage, PermissionResult, HookInput |
sdk/runtimeTypes.ts |
Non-serializable types with callbacks and method interfaces | SDKSession, Options, Query, SdkMcpToolDefinition |
sdk/controlTypes.ts |
Control protocol for SDK builders (bridge subpath consumers) | SDKControlRequest, SDKControlResponse |
sdk/settingsTypes.generated.ts |
Full Settings type generated from settings JSON schema | Settings |
Top-Level SDK Functions
// V1 API (stable) — headless one-shot query
export function query(params: {
prompt: string | AsyncIterable<SDKUserMessage>
options?: Options
}): Query
// V2 API (@alpha) — persistent multi-turn sessions
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 listSessions(options?: ListSessionsOptions): Promise<SDKSessionInfo[]>
export async function getSessionInfo(sessionId: string): Promise<SDKSessionInfo | undefined>
export async function getSessionMessages(sessionId: string): Promise<SessionMessage[]>
export async function renameSession(sessionId: string, title: string): Promise<void>
export async function tagSession(sessionId: string, tag: string | null): Promise<void>
export async function forkSession(sessionId: string, options?: ForkSessionOptions): Promise<ForkSessionResult>
// In-process MCP server
export function createSdkMcpServer(options: { name: string; tools: SdkMcpToolDefinition[] }): McpSdkServerConfigWithInstance
export function tool<S>(name, description, schema, handler): SdkMcpToolDefinition<S>
06 The Control Protocol (SDK Builders)
External SDK implementations (like the Python claude-code-sdk) communicate with
the Claude Code process via a JSON-based control protocol layered over stdio.
The schemas are defined in sdk/controlSchemas.ts.
Control Request Subtypes
| Subtype | Direction | Purpose |
|---|---|---|
initialize | SDK → CLI | Start session — pass hooks, MCP servers, agents, system prompt overrides |
interrupt | SDK → CLI | Cancel the currently running turn |
can_use_tool | CLI → SDK | Request permission for a tool use; SDK host responds allow/deny |
set_permission_mode | SDK → CLI | Change permission mode mid-session (default / acceptEdits / bypassPermissions / plan / dontAsk) |
set_model | SDK → CLI | Switch model for subsequent turns |
set_max_thinking_tokens | SDK → CLI | Adjust extended thinking budget |
mcp_status | SDK → CLI | Query current MCP server connection states |
get_context_usage | SDK → CLI | Inspect context window utilization by category |
// Initialize request — sent by SDK to start a session
{
subtype: "initialize",
hooks: {
"PreToolUse": [{ hookCallbackIds: ["my-hook"], matcher: "Bash" }]
},
sdkMcpServers: ["my-server"],
systemPrompt: "You are a coding assistant.",
agents: {
"reviewer": { description: "Reviews code changes", ... }
}
}
// Initialize response — returned by CLI
{
commands: [...], // available slash commands
agents: [...], // available agent types
models: [...], // available models
account: {...}, // account info
pid: 12345 // @internal CLI PID for tmux socket isolation
}
07 Hook Events — The SDK Observer Pattern
The SDK exposes a rich hook system that lets external hosts observe and intercept Claude Code's
lifecycle. There are 26 named hook events, defined in sdk/coreTypes.ts:
export const HOOK_EVENTS = [
// Tool execution
'PreToolUse', 'PostToolUse', 'PostToolUseFailure',
// Permission flow
'PermissionRequest', 'PermissionDenied',
// Session lifecycle
'SessionStart', 'SessionEnd', 'Setup',
// Turn lifecycle
'Stop', 'StopFailure',
// Context management
'PreCompact', 'PostCompact',
// Agent/swarm lifecycle
'SubagentStart', 'SubagentStop', 'TeammateIdle',
'TaskCreated', 'TaskCompleted',
// Notifications and user input
'Notification', 'UserPromptSubmit',
// Config changes
'ConfigChange', 'InstructionsLoaded', 'CwdChanged', 'FileChanged',
// Elicitation
'Elicitation', 'ElicitationResult',
// Worktree
'WorktreeCreate', 'WorktreeRemove',
] as const
Every hook fires with a BaseHookInput that includes session_id,
transcript_path, cwd, agent_id (if inside a subagent),
and agent_type. Each event adds its own specific fields on top.
agent_id (present only inside a subagent) — not agent_type — to
distinguish subagent hook firings from main-thread firings. The main thread can have an
agent_type when started with --agent but will never have an agent_id.
08 Daemon & Bridge Mode (@internal)
For advanced host architectures (desktop apps, CI systems, claude.ai integrations),
agentSdkTypes.ts also exports @internal primitives:
Scheduled Tasks / Cron
// Watch .claude/scheduled_tasks.json and yield fire/missed events
export function watchScheduledTasks(opts: {
dir: string
signal: AbortSignal
getJitterConfig?: () => CronJitterConfig
}): ScheduledTasksHandle
// ScheduledTasksHandle — drain with for await
{
events(): AsyncGenerator<ScheduledTaskEvent> // fire | missed
getNextFireTime(): number | null // epoch ms of next scheduled run
}
This lets daemon processes own the scheduler in the parent process. When a task fires, the
daemon spawns a query() subprocess — if it crashes, the daemon can respawn it
while the schedule continues.
Remote Control / Bridge
// Connect the local machine as a claude.ai remote-control environment
export async function connectRemoteControl(opts: ConnectRemoteControlOptions):
Promise<RemoteControlHandle | null>
// RemoteControlHandle — two-way bridge over WebSocket
{
sessionUrl: string
environmentId: string
bridgeSessionId: string
write(msg: SDKMessage): void // pipe query() yields in
sendResult(): void // signal turn complete
inboundPrompts(): AsyncGenerator<...> // read user messages from claude.ai
controlRequests(): AsyncGenerator<...> // interrupt, set_model, etc.
permissionResponses(): AsyncGenerator<...>
onStateChange(cb): void // ready | connected | reconnecting | failed
teardown(): Promise<void>
}
query.enableRemoteControl: the WebSocket lives in the child process
and dies with it. Use daemon mode for production-grade reliability.
09 sandboxTypes.ts — Process Isolation Config
src/entrypoints/sandboxTypes.ts is the single source of truth for sandbox
configuration types. Both the SDK and the settings validation system import from here.
It is exported through agentSdkTypes.ts so SDK consumers can configure sandboxing.
// SandboxSettings — full configuration for process-level isolation
{
enabled: boolean
failIfUnavailable: boolean // hard-gate for managed deployments
autoAllowBashIfSandboxed: boolean
allowUnsandboxedCommands: boolean
network: {
allowedDomains: string[]
allowManagedDomainsOnly: boolean // enterprise: only managed domains
allowUnixSockets: string[] // macOS only
allowLocalBinding: boolean
httpProxyPort: number
socksProxyPort: number
}
filesystem: {
allowWrite: string[] // merged with Edit() allow rules
denyWrite: string[] // merged with Edit() deny rules
denyRead: string[]
allowRead: string[] // re-allow within denyRead regions
allowManagedReadPathsOnly: boolean
}
}
10 main.tsx — Invocation Modes
Once cli.tsx falls through to main.tsx, the Commander-based CLI
handles the remaining invocation modes. Key ones:
| Flag / Mode | Behavior |
|---|---|
-p / --print <prompt> |
Headless mode — runs a single prompt non-interactively, streams results to stdout, exits. Used by scripts and the Agent SDK. |
--mcp |
Starts Claude Code as an MCP server via stdio. Delegates to entrypoints/mcp.ts. |
| (no flags) | Interactive REPL — renders the full Ink TUI. Calls launchRepl(). |
--resume [sessionId] |
Resume a previous session by UUID, or show the resume chooser TUI. |
--continue |
Continue the most recent session without showing the chooser. |
--dangerously-skip-permissions |
Bypass all permission checks (requires explicit opt-in in settings). Used by CI environments. |
--allowedTools |
Comma-separated tool allow-list for the session. |
--sdk-transport=process |
SDK process transport mode — connects the Agent SDK via stdin/stdout control protocol. |
11 Complete Invocation Flow
Key Takeaways
cli.tsxis a pure dispatcher — it fast-paths 8+ special commands before loading the full CLI, keeping startup snappy for version checks, daemon workers, and bridge mode.- Build-time
feature()flags perform dead-code elimination — bridge mode, daemon, background sessions, and other features are completely absent from external builds unless enabled. init.tsis memoized and shared by all entrypoints — it coordinates TLS certs, proxy agents, OAuth, telemetry, and cleanup handlers before the first API call.- Claude Code can be an MCP server (
--mcpmode viamcp.ts) while also consuming MCP servers — the boundary is symmetric. - The Agent SDK public API (
agentSdkTypes.ts) is a stub file — all function bodies throw; the real implementations are injected by the SDK transport at runtime. - 26 lifecycle hook events let SDK hosts observe and intercept tool execution, permission decisions, session lifecycle, compaction, and worktree operations.
- The control protocol separates SDK consumers (who call
query()) from SDK builders (who implement the process transport and speak the control protocol directly). sandboxTypes.tsis the single source of truth for process isolation config — used by both the SDK export and the settings validation system.
Check Your Understanding
claude --version and why does it load zero modules?cli.tsx reads process.argv.slice(2) synchronously before any imports. MACRO.VERSION is a build-time inline, so no dynamic import is ever issued.cli.tsx acts before any module is dynamically imported, checking argv directly with MACRO.VERSION inlined at build time.agentSdkTypes.ts throw 'not implemented'. What mechanism actually provides the real implementations?--sdk-transport=process, the transport layer provides real implementations via the control protocol over stdin/stdout.agentSdkTypes.ts serves as the type-safe API surface. The SDK transport injects real implementations at runtime when the process is actually running.query.enableRemoteControl?connectRemoteControl JSDoc. With daemon mode, the parent holds the WebSocket so it survives agent subprocess crashes, enabling respawning while claude.ai keeps the same session.connectRemoteControl explains: daemon mode keeps the WebSocket in the parent process. If the agent child crashes, the WebSocket survives and the daemon can respawn the agent.feature() in cli.tsx differ from a runtime GrowthBook gate like isBridgeEnabled()?feature() must stay inline for build-time dead code elimination. The bridge block, daemon block, etc. are entirely absent from external distribution builds if the feature is disabled.feature() is a Bun build-time DCE gate. Code inside disabled feature blocks is stripped from the binary, not just branched-over at runtime.