Why this matters
Claude Code is easier to learn if you separate orchestration from the long-lived
runtime it launches. main.tsx wires the boot path together, primes context
before the query loop is live, and then hands control to the REPL that keeps
the session interactive.
Big picture first
The top-level files in claude-code already expose the main shape of the system:
main.tsx starts the app, context.ts prepares context, query.ts and
QueryEngine.ts drive the turn lifecycle, Tool.ts and tools.ts define the
action layer, and commands.ts defines user-facing slash commands.
Claude Code in one pass
One user turn moves from startup and context gathering into the query engine, tool execution, and REPL updates.
From CLI Entry To Live REPL
The boot path is easiest to understand if you treat main.tsx as the
orchestration layer. It does not contain every subsystem’s implementation; it
coordinates setup, warms the context, and prepares the state that the session
needs before interactive work begins.
This is the interactive path, not the non-interactive --print path. The boot
code still primes tools, commands, and agent definitions up front, but the
interactive session keeps the live handoff separate from the headless case.
profileCheckpoint('main_tsx_entry');
maybeActivateProactive(options);
let tools = getTools(toolPermissionContext);
const setupPromise = setup(
preSetupCwd,
permissionMode,
allowDangerouslySkipPermissions,
worktreeEnabled,
worktreeName,
tmuxEnabled,
sessionId ? validateUuid(sessionId) : undefined,
worktreePRNumber,
messagingSocketPath,
);
const commandsPromise = worktreeEnabled ? null : getCommands(preSetupCwd);
const agentDefsPromise = worktreeEnabled ? null : getAgentDefinitionsWithOverrides(preSetupCwd);
await setupPromise;
const [commands, agentDefinitionsResult] = await Promise.all([
commandsPromise ?? getCommands(currentCwd),
agentDefsPromise ?? getAgentDefinitionsWithOverrides(currentCwd),
]);
Once boot work is done, await launchRepl(root, { is the handoff point.
main.tsx stops owning the startup choreography and passes control into the
long-lived interactive session.
const initialMessages = deepLinkBanner
? [deepLinkBanner, ...hookMessages]
: hookMessages.length > 0
? hookMessages
: undefined;
await launchRepl(root, {
getFpsMetrics,
stats,
initialState,
}, {
...sessionConfig,
initialMessages,
pendingHookMessages,
}, renderAndRun);
The first render is not the end of warmup. renderAndRun() renders the REPL,
starts deferred prefetches, and then waits for the session to exit. That is
where context warmup continues after the UI is already live.
export async function renderAndRun(root: Root, element: React.ReactNode): Promise<void> {
root.render(element);
startDeferredPrefetches();
await root.waitUntilExit();
await gracefulShutdown(0);
}
export function startDeferredPrefetches(): void {
void initUser();
void getUserContext();
prefetchSystemContextIfSafe();
void getRelevantTips();
}
The Action And Command Registries
Claude Code does not hard-code every tool and command directly inside
main.tsx. Instead, the boot path asks registry files to assemble the concrete
tool and command sets from smaller pieces, which keeps the orchestration layer
thin and the subsystem boundaries readable.
tools.ts assembles the action layer for the model. It defines the base tools
the model can call, including the core agent and shell actions, and it can add
or skip embedded-search-related tools based on runtime capability.
export function getAllBaseTools(): Tools {
return [
AgentTool,
TaskOutputTool,
BashTool,
// Ant-native builds have bfs/ugrep embedded in the bun binary (same ARGV0
// trick as ripgrep). When available, find/grep in Claude's shell are aliased
// to these fast tools, so the dedicated Glob/Grep tools are unnecessary.
...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
ExitPlanModeV2Tool,
FileReadTool,
FileEditTool,
FileWriteTool,
NotebookEditTool,
WebFetchTool,
TodoWriteTool,
WebSearchTool,
TaskStopTool,
AskUserQuestionTool,
SkillTool,
EnterPlanModeTool,
commands.ts assembles the user-facing slash-command layer. It loads the
available commands, filters them for the current workspace, and keeps the
interactive command surface separate from the model-facing tool pool.
what to notice in the list: fixed built-ins first, then conditional spreads, then the internal-only tail.
const COMMANDS = memoize((): Command[] => [
addDir,
advisor,
agents,
branch,
btw,
chrome,
clear,
color,
compact,
config,
copy,
desktop,
context,
contextNonInteractive,
cost,
diff,
doctor,
effort,
exit,
fast,
files,
heapDump,
help,
ide,
init,
keybindings,
installGitHubApp,
installSlackApp,
mcp,
memory,
mobile,
model,
outputStyle,
remoteEnv,
plugin,
pr_comments,
releaseNotes,
reloadPlugins,
rename,
resume,
session,
skills,
stats,
status,
statusline,
stickers,
tag,
theme,
feedback,
review,
ultrareview,
rewind,
securityReview,
terminalSetup,
upgrade,
extraUsage,
extraUsageNonInteractive,
rateLimitOptions,
usage,
usageReport,
vim,
...(webCmd ? [webCmd] : []),
...(forkCmd ? [forkCmd] : []),
...(buddy ? [buddy] : []),
...(proactive ? [proactive] : []),
...(briefCommand ? [briefCommand] : []),
...(assistantCommand ? [assistantCommand] : []),
...(bridge ? [bridge] : []),
...(remoteControlServerCommand ? [remoteControlServerCommand] : []),
...(voiceCommand ? [voiceCommand] : []),
thinkback,
thinkbackPlay,
permissions,
plan,
privacySettings,
hooks,
exportCommand,
sandboxToggle,
...(!isUsing3PServices() ? [logout, login()] : []),
passes,
...(peersCmd ? [peersCmd] : []),
tasks,
...(workflowsCmd ? [workflowsCmd] : []),
...(torch ? [torch] : []),
...(process.env.USER_TYPE === 'ant' && !process.env.IS_DEMO
? INTERNAL_ONLY_COMMANDS
: []),
])
getCommands() turns that registry into the workspace-specific command list.
It applies availability checks, folds in dynamic skills, and then returns the
final interactive surface.
export async function getCommands(cwd: string): Promise<Command[]> {
const allCommands = await loadAllCommands(cwd)
// Get dynamic skills discovered during file operations
const dynamicSkills = getDynamicSkills()
// Build base commands without dynamic skills
const baseCommands = allCommands.filter(
_ => meetsAvailabilityRequirement(_) && isCommandEnabled(_),
)
if (dynamicSkills.length === 0) {
return baseCommands
}
// Dedupe dynamic skills - only add if not already present
const baseCommandNames = new Set(baseCommands.map(c => c.name))
const uniqueDynamicSkills = dynamicSkills.filter(
s =>
!baseCommandNames.has(s.name) &&
meetsAvailabilityRequirement(s) &&
isCommandEnabled(s),
)
if (uniqueDynamicSkills.length === 0) {
return baseCommands
}
// Insert dynamic skills after plugin skills but before built-in commands
const builtInNames = new Set(COMMANDS().map(c => c.name))
const insertIndex = baseCommands.findIndex(c => builtInNames.has(c.name))
if (insertIndex === -1) {
return [...baseCommands, ...uniqueDynamicSkills]
}
return [
...baseCommands.slice(0, insertIndex),
...uniqueDynamicSkills,
...baseCommands.slice(insertIndex),
]
}
Read together, these registries show the split in the runtime. tools.ts
answers the question “what can the model do right now?”, while commands.ts
answers the question “what can the person type at the prompt?”
Code Layout At A Glance
main.tsx
Boots the application, prepares the session, and hands control to the REPL.
context.ts, constants/prompts.ts, utils/queryContext.ts
Shape the system and user context before a turn runs.
query.ts, QueryEngine.ts
Own the turn lifecycle and model interaction.
Tool.ts, tools.ts, tools/
Define the shared tool contract and the concrete tool pool.
commands.ts, commands/
Define the slash-command surface and dynamic command loading.
tasks/
Holds longer-lived or background work that does not fit a single tool call.
state/, screens/
Hold the interactive shell state and REPL-facing UI flow.
services/
Contains support systems like MCP, analytics, compacting, API access, and more.
bridge/, remote/
Hold non-local or connected-mode behavior that extends the same core runtime.
How this part breaks down
startup-and-boot-sequenceStart with howmain.tsxgets a session off the ground.context-and-prompt-constructionSee how repo state, memory, and prompt sections become model-visible context.the-query-engine-and-turn-lifecycleFollow one full model turn from prepared messages to streamed output.tools-and-permission-modelLearn how Claude Code turns model intent into executable actions.tasks-agents-and-background-executionSee how long-running work and subagents fit around the main loop.interaction-shell-and-app-stateLearn how the REPL and app state hold the interactive session together.commands-and-user-controlSee how slash commands expose user-facing control paths.extensions-skills-plugins-and-mcpLearn how Claude Code grows beyond its built-in behavior.remote-bridge-and-connected-modesSee how non-local and connected modes plug into the same architecture.services-behind-the-scenesLearn which support layers keep the runtime working.special-features-and-memorable-cornersLeave memorable extras like buddy and voice for after the main model is clear.
Takeaways
- The architecture chapter is a map, not a replacement for the deeper subtree chapters.
- main.tsx orchestrates, but it does not own every subsystem.
- The registries in tools.ts and commands.ts bridge architecture to concrete behavior.
Fun Facts
- The codebase has memorable side systems like buddy and bridge, but they make more sense once the main loop is clear.