cli-modes-and-flags:Claude Code CLI 的启动模式和关键参数
Claude Code 的命令行界面(CLI)远超一个简单的输入框。它通过复杂的参数解析和模式切换,支撑了从“交互式结对编程”到“自动化脚本执行”的多种场景。
从定义开始
本质上,它是一个基于 commander.js 构建的命令行分发器。它负责把用户的原始 Shell 输入转化为 AppState 的初始配置,并决定最终是进入 launchRepl(交互式界面)还是 runHeadless(静默打印)。
代码里的真实逻辑
主要的命令行定义和处理逻辑位于 claude-code-opensource/src/main.tsx。
1. 核心启动模式(Mode Selection)
源码通过识别不同的 Flag 来分流执行路径:
- Interactive Mode(默认模式): 如果不带
-p参数,系统会通过launchRepl启动基于 React Ink 的终端 UI。 - One-shot Mode (
-p,--print): 通过src/cli/print.ts中的runHeadless实现。它会执行推理循环,直接将结果输出到 stdout,然后进程退出。 - Resume Mode (
-r,--resume [session]): 如果指定了-r,源码会调用loadConversationForResume。它会去~/.claude/sessions/目录下根据 UUID 或标题模糊匹配历史记录,将其加载进当前会话。 - Continue Mode (
-c,--continue): 这是-r的快捷方式,直接加载“最后一次”未完成的会话。
2. 关键参数的传递路径
--model: 在main.tsx中解析后,被注入到ToolUseContext的options.mainLoopModel。如果用户没填,则从getInitialSettings()中读取默认值(通常是claude-3-7-sonnet-latest)。--max-turns: 直接映射到推理引擎的maxTurns约束。如果不传,源码通常不设上限或使用内置的安全水位线。--output-format: 支持text和json。这会改变输出样式,同时也影响模型收到的System Prompt,要求其以特定结构返回数据。
3. 模式对推理循环的影响
模式决定了 PermissionMode。
- 在交互模式下,默认权限通常是
default(遇事弹窗)。 - 在
-p模式下,如果用户没有显式授权,很多具有副作用的工具(如bash)可能会因为无法弹出 TTY 确认框而报错退出。
踩坑指南
- 参数优先级:命令行 Flag > 环境变量 > 本地配置文件(
~/.claude/settings.json)。 -p模式的阻塞性:在-p模式下,Claude Code 依然会运行完整的 Agentic Loop。如果模型陷入工具死循环,你必须通过--max-turns来强行勒马。- 隐式 Resume:如果你不带参数启动,且配置了
autoResume(默认开启),Claude 会尝试自动恢复之前的上下文,这由src/main.tsx的初始加载逻辑控制。
接下来看什么
- 想看 TUI 怎么渲染?查看
claude-code-opensource/src/replLauncher.tsx。 - 想看如何加载历史?查看
claude-code-opensource/src/history.ts。
源码锚点
- claude-code-opensource/src/main.tsx:968 —
program.name('claude')CLI 定义的起点。
📄 src/main.tsx — `program.name('claude')` CLI 定义的起点。
tsx
void loadPolicyLimits();
profileCheckpoint('preAction_after_remote_settings');
// Load settings sync (non-blocking, fail-open)
// CLI: uploads local settings to remote (CCR download is handled by print.ts)
if (feature('UPLOAD_USER_SETTINGS')) {
void import('./services/settingsSync/index.js').then(m => m.uploadUserSettingsInBackground());
}
profileCheckpoint('preAction_after_settings_sync');
});
program.name('claude').description(`Claude Code - starts an interactive session by default, use -p/--print for non-interactive output`).argument('[prompt]', 'Your prompt', String)
// Subcommands inherit helpOption via commander's copyInheritedSettings —
// setting it once here covers mcp, plugin, auth, and all other subcommands.
.helpOption('-h, --help', 'Display help for command').option('-d, --debug [filter]', 'Enable debug mode with optional category filtering (e.g., "api,hooks" or "!1p,!file")', (_value: string | true) => {
// If value is provided, it will be the filter string
// If not provided but flag is present, value will be true
// The actual filtering is handled in debug.ts by parsing process.argv
return true;
}).addOption(new Option('-d2e, --debug-to-stderr', 'Enable debug mode (to stderr)').argParser(Boolean).hideHelp()).option('--debug-file <path>', 'Write debug logs to a specific file path (implicitly enables debug mode)', () => true).option('--verbose', 'Override verbose mode setting from config', () => true).option('-p, --print', 'Print response and exit (useful for pipes). Note: The workspace trust dialog is skipped when Claude is run with the -p mode. Only use this flag in directories you trust.', () => true).option('--bare', 'Minimal mode: skip hooks, LSP, plugin sync, attribution, auto-memory, background prefetches, keychain reads, and CLAUDE.md auto-discovery. Sets CLAUDE_CODE_SIMPLE=1. Anthropic auth is strictly ANTHROPIC_API_KEY or apiKeyHelper via --settings (OAuth and keychain are never read). 3P providers (Bedrock/Vertex/Foundry) use their own credentials. Skills still resolve via /skill-name. Explicitly provide context via: --system-prompt[-file], --append-system-prompt[-file], --add-dir (CLAUDE.md dirs), --mcp-config, --settings, --agents, --plugin-dir.', () => true).addOption(new Option('--init', 'Run Setup hooks with init trigger, then continue').hideHelp()).addOption(new Option('--init-only', 'Run Setup and SessionStart:startup hooks, then exit').hideHelp()).addOption(new Option('--maintenance', 'Run Setup hooks with maintenance trigger, then continue').hideHelp()).addOption(new Option('--output-format <format>', 'Output format (only works with --print): "text" (default), "json" (single result), or "stream-json" (realtime streaming)').choices(['text', 'json', 'stream-json'])).addOption(new Option('--json-schema <schema>', 'JSON Schema for structured output validation. ' + 'Example: {"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}').argParser(String)).option('--include-hook-events', 'Include all hook lifecycle events in the output stream (only works with --output-format=stream-json)', () => true).option('--include-partial-messages', 'Include partial message chunks as they arrive (only works with --print and --output-format=stream-json)', () => true).addOption(new Option('--input-format <format>', 'Input format (only works with --print): "text" (default), or "stream-json" (realtime streaming input)').choices(['text', 'stream-json'])).option('--mcp-debug', '[DEPRECATED. Use --debug instead] Enable MCP debug mode (shows MCP server errors)', () => true).option('--dangerously-skip-permissions', 'Bypass all permission checks. Recommended only for sandboxes with no internet access.', () => true).option('--allow-dangerously-skip-permissions', 'Enable bypassing all permission checks as an option, without it being enabled by default. Recommended only for sandboxes with no internet access.', () => true).addOption(new Option('--thinking <mode>', 'Thinking mode: enabled (equivalent to adaptive), disabled').choices(['enabled', 'adaptive', 'disabled']).hideHelp()).addOption(new Option('--max-thinking-tokens <tokens>', '[DEPRECATED. Use --thinking instead for newer models] Maximum number of thinking tokens (only works with --print)').argParser(Number).hideHelp()).addOption(new Option('--max-turns <turns>', 'Maximum number of agentic turns in non-interactive mode. This will early exit the conversation after the specified number of turns. (only works with --print)').argParser(Number).hideHelp()).addOption(new Option('--max-budget-usd <amount>', 'Maximum dollar amount to spend on API calls (only works with --print)').argParser(value => {
const amount = Number(value);
if (isNaN(amount) || amount <= 0) {- claude-code-opensource/src/main.tsx:3487 —
launchRepl的调用点。 - claude-code-opensource/src/cli/print.ts:993 —
runHeadless处理-p逻辑的地方。
📄 src/cli/print.ts — `runHeadless` 处理 `-p` 逻辑的地方。
typescript
sdkMcpConfigs: Record<string, McpSdkServerConfig>,
getAppState: () => AppState,
setAppState: (f: (prev: AppState) => AppState) => void,
agents: AgentDefinition[],
options: {
verbose: boolean | undefined
jsonSchema: Record<string, unknown> | undefined
permissionPromptToolName: string | undefined
allowedTools: string[] | undefined
thinkingConfig: ThinkingConfig | undefined
maxTurns: number | undefined
maxBudgetUsd: number | undefined
taskBudget: { total: number } | undefined
systemPrompt: string | undefined
appendSystemPrompt: string | undefined
userSpecifiedModel: string | undefined
fallbackModel: string | undefined
replayUserMessages?: boolean | undefined
includePartialMessages?: boolean | undefined
enableAuthStatus?: boolean | undefined
agent?: string | undefined