default-and-acceptedits-modes:Default 与 AcceptEdits 模式的运行时行为
在 Claude Code 中,权限模式(Permission Mode)直接决定了 Agent 的“手脚”被束缚到什么程度。其中最常用的两种——default 和 acceptEdits——在源码层面上有着截然不同的判定路径。
一句话讲清楚
本质上,这是 执行策略的全局开关。
- Default:保守派。默认所有涉及系统变更的操作(写文件、运行命令)都必须经过人类确认。
- AcceptEdits:效率派。它假定模型修改代码是安全的(或可撤销的),因此自动放行所有文件写入,但依然拦截 Bash 命令。
从源码看实现
模式的枚举和基本属性定义在 claude-code-opensource/src/utils/permissions/PermissionMode.ts。但真正的行为分水岭在于各工具的 checkPermissions 实现。
1. Default 模式:全员拦截
在 default 模式下,toolPermissionContext.mode 为 default。 当 FileEditTool 被调用时:
- 进入
src/utils/permissions/filesystem.ts的checkWritePermissionForTool。 - 代码会检查是否存在显式的
allow规则。由于是default模式,通常没有。 - 逻辑落入最后的兜底逻辑:
return { behavior: 'ask' }。 - 这会导致 UI 层弹出
(y/n)确认框。
2. AcceptEdits 模式:针对性放行
这是 AcceptEdits 模式最精彩的部分。在 src/utils/permissions/filesystem.ts 的第 1366 行左右:
typescript
if (toolPermissionContext.mode === 'acceptEdits' && isInWorkingDir) {
return {
behavior: 'allow',
// ...
}
}1
2
3
4
5
6
2
3
4
5
6
- 它的边界:仅当
isInWorkingDir(在当前项目目录下)且是“编辑”操作时,它才会返回allow。 - 它不放行什么:虽然它放行了文件修改,但它 完全不放行
bash工具(除非是只读命令)。在src/tools/BashTool的相关检查中,如果是acceptEdits模式,它依然会要求用户确认除了ls、pwd等安全命令之外的所有操作。
边界条件
- AcceptEdits 不是 Bypass:它只对
FileEditTool和FileWriteTool宽容。如果你让 Claude 运行npm install,在acceptEdits模式下你依然得敲y。 - 符号的含义:在 UI 状态栏,
acceptEdits对应的是双箭头⏵⏵。这在PermissionMode.ts中被定义,代表“快进”编辑过程。 - 安全性隔离:即便在
acceptEdits模式下,修改.git目录或.claude.json依然会被safetyCheck拦截,并强行降级为ask。这在filesystem.ts的DANGEROUS_FILES检查中体现。
继续探索
- 想看危险文件名单?查看
claude-code-opensource/src/utils/permissions/filesystem.ts的DANGEROUS_FILES常量。 - 想看自动模式(Auto Mode)与它们的区别?查看
claude-code-opensource/src/utils/permissions/classifierDecision.ts。
源码锚点
- claude-code-opensource/src/utils/permissions/PermissionMode.ts:59 —
acceptEdits的 UI 配置定义。
📄 src/utils/permissions/PermissionMode.ts — `acceptEdits` 的 UI 配置定义。L49-69 of 142
typescript
color: 'text',
external: 'default',
},
plan: {
title: 'Plan Mode',
shortTitle: 'Plan',
symbol: PAUSE_ICON,
color: 'planMode',
external: 'plan',
},
acceptEdits: {
title: 'Accept edits',
shortTitle: 'Accept',
symbol: '⏵⏵',
color: 'autoAccept',
external: 'acceptEdits',
},
bypassPermissions: {
title: 'Bypass Permissions',
shortTitle: 'Bypass',
symbol: '⏵⏵',1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- claude-code-opensource/src/utils/permissions/filesystem.ts:1366 —
acceptEdits自动放行逻辑的硬编码位置。
📄 src/utils/permissions/filesystem.ts — `acceptEdits` 自动放行逻辑的硬编码位置。L1356-1376 of 1778
typescript
}
}
}
// 3. If in acceptEdits or sandboxBashMode mode, allow all writes in original cwd
const isInWorkingDir = pathInAllowedWorkingPath(
path,
toolPermissionContext,
pathsToCheck,
)
if (toolPermissionContext.mode === 'acceptEdits' && isInWorkingDir) {
return {
behavior: 'allow',
updatedInput: input,
decisionReason: {
type: 'mode',
mode: toolPermissionContext.mode,
},
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- claude-code-opensource/src/tools/FileEditTool/FileEditTool.ts:125 — 文件编辑工具如何调用权限检查引擎。
📄 src/tools/FileEditTool/FileEditTool.ts — 文件编辑工具如何调用权限检查引擎。L115-135 of 626
typescript
backfillObservableInput(input) {
// hooks.mdx documents file_path as absolute; expand so hook allowlists
// can't be bypassed via ~ or relative paths.
if (typeof input.file_path === 'string') {
input.file_path = expandPath(input.file_path)
}
},
async preparePermissionMatcher({ file_path }) {
return pattern => matchWildcardPattern(pattern, file_path)
},
async checkPermissions(input, context): Promise<PermissionDecision> {
const appState = context.getAppState()
return checkWritePermissionForTool(
FileEditTool,
input,
appState.toolPermissionContext,
)
},
renderToolUseMessage,
renderToolResultMessage,
renderToolUseRejectedMessage,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21