读完这篇你能:用
canUseTool在运行时接管审批,并把用户的选择记成规则。前置:权限配置。约 25 分钟。
API
权限审批不同通过消息发出的。
通过 claude agent sdk 发出请求时,可以使用options的 canUseTool 字段注册回调函数。
注意一下,下边这个函数类型的定义,前边都是入参,用来给我们使用的。返回值是一个promise
/**
* 用于控制工具是否可用的权限回调。
* 每次真正执行工具之前都会调用,用来判断这次调用是否允许。
*
* 只有在调用方已经通过带外通道发过 control_response 之后,才应返回 `null`
* (例如用签名后的 HTTP POST 回传 `requestId`);此时 SDK 会跳过自己的传输层写入。
* 失败即关闭:如果不小心返回了 null,就不会发出 control_response,工具会一直卡在阻塞状态
* ——权限提示没有超时/停放截止时间。
*/
export declare type CanUseTool = (
toolName: string,
input: Record<string, unknown>,
options: {
/** 若操作应中止,会通过该信号通知。 */
signal: AbortSignal;
/**
* 建议的权限更新,目的是让用户本会话内不再反复被询问同一工具。
*
* 典型用法:如果 UI 提供「始终允许」之类选项,
* 用户选中后应把这份完整 suggestions 作为 PermissionResult 的
* `updatedPermissions` 返回。
*/
suggestions?: PermissionUpdate[];
/**
* 触发这次权限请求的文件路径(如果有)。
* 例如 Bash 命令试图访问允许目录之外的路径时。
*/
blockedPath?: string;
/** 说明为什么会弹出这次权限请求。 */
decisionReason?: string;
/**
* bridge 渲染好的完整权限提示句(例如
* "Claude wants to read foo.txt")。有这个字段时,应直接当主提示文案,
* 不要再自己用 toolName + input 拼一句。
*/
title?: string;
/**
* 工具动作的短名词短语(例如 "Read file"),
* 适合做按钮文案或紧凑 UI。
*/
displayName?: string;
/**
* 来自 bridge 的可读副标题(例如
* "Claude will have read and write access to files in ~/Downloads")。
*/
description?: string;
/**
* 这次工具调用在 assistant 消息里的唯一 ID。
* 同一条 assistant 消息里的多个工具调用会有不同的 toolUseID。
*/
toolUseID: string;
/** 如果当前跑在子代理上下文里,则为该子代理的 ID。 */
agentID?: string;
/**
* control_request 信封上的 `request_id`。走带外回复的 control_response
* (例如签名 HTTP POST,而不是 SDK 的 WebSocket 写入)
* 必须原样回传这个值,worker 才能对上对应请求。
*/
requestId: string;
/**
* 当用户配置的 ask 规则(permissions.ask)强制弹出这次提示,
* 同时 ask 还带着工具自己的 decisionReason 时会设置。
* Host 若按原因做策略(例如对 safetyCheck 自动拒绝),
* 或做 Host 侧自动批准,遇到带这个字段的 ask 应视为「规则强制弹窗」:
* 用户的明确意图就是必须问人。
*/
matchedAskRule?: {
source: string;
toolName: string;
ruleContent?: string;
};
},
) => Promise<PermissionResult | null>;
在权限处理这块,语言描述不方便,我绘制了一个时序图

请求参数解释
- input
input 是这次工具调用的入参对象(Claude 真正要交给工具的参数),类型是 Record<string, unknown>。toolName 告诉你调哪个工具,input 告诉你怎么调。
这里有个例子:{ "command": "ls -la", "description": "列出当前目录的所有文件和目录" }
其他字段没必要详细解释,感兴趣的大概看下上边代码的注释即可。
审核结果参数解释
返回的 PermissionResult 结构如下,通常我们只需要涉及到 允不允许即可。大概率只会用到 behavior 字段。
export declare type PermissionResult = {
behavior: 'allow';
updatedInput?: Record<string, unknown>; // 如果需要变更工具执行的命令,可以传一个更新后的input
updatedPermissions?: PermissionUpdate[];
toolUseID?: string;
decisionClassification?: PermissionDecisionClassification;
} | {
behavior: 'deny';
message: string;
interrupt?: boolean;
toolUseID?: string;
decisionClassification?: PermissionDecisionClassification;
};
记住审批结果
上边我们介绍的都是本次权限申请是否允许。但是,我们在claude cli 中对话时,claude 经常会在权限申请时提出一个B选项:当工作目录始终允许执行该操作。

其实这就是 批准某项操作后还希望”记住这次选择”,让用户不再被重复询问同一个工具。这里我们就来详细介绍一下 PermissionUpdate 参数。
PermissionUpdate:批准后把规则写进哪里
它是一组操作,靠 type 区分,共六种,覆盖三类:规则(add / replace / remove)、模式(setMode)、目录(add / remove directories)。
type PermissionUpdate =
| {
type: "addRules";
rules: PermissionRuleValue[];
behavior: PermissionBehavior;
destination: PermissionUpdateDestination;
}
| {
type: "replaceRules";
rules: PermissionRuleValue[];
behavior: PermissionBehavior;
destination: PermissionUpdateDestination;
}
| {
type: "removeRules";
rules: PermissionRuleValue[];
behavior: PermissionBehavior;
destination: PermissionUpdateDestination;
}
| {
type: "setMode";
mode: PermissionMode;
destination: PermissionUpdateDestination;
}
| {
type: "addDirectories";
directories: string[];
destination: PermissionUpdateDestination;
}
| {
type: "removeDirectories";
directories: string[];
destination: PermissionUpdateDestination;
};
六个操作
type |
带哪些字段 | 干什么 |
|---|---|---|
addRules |
rules + behavior + destination |
新增规则 |
replaceRules |
rules + behavior + destination |
用 rules 整体替换目标处已有规则,适合把之前宽松的规则收紧 |
removeRules |
rules + behavior + destination |
删除规则。仍要 behavior:规则按「工具 + 行为」存,缺了定位不到 |
setMode |
mode + destination |
直接切换权限模式(暂时不涉及) |
addDirectories |
directories + destination |
把目录加进受信任清单 |
removeDirectories |
directories + destination |
从清单移除,之后这些目录重新触发权限检查。没有 replaceDirectories,要替换就先删再加 |
规则长什么样:PermissionRuleValue
type PermissionRuleValue = { toolName: string; ruleContent?: string; };
toolName:针对哪个工具,如Bash/EditruleContent:可选。Bash 通常是允许的命令模式;省略表示该工具的所有调用
行为:PermissionBehavior
"allow" 放行 / "deny" 拒绝 / "ask" 仍询问。
写到哪:PermissionUpdateDestination
| 目标 | 生效范围 |
|---|---|
userSettings |
用户全局,所有项目 |
projectSettings |
项目级,会进 git 共享 |
localSettings |
项目本地 .claude/settings.local.json,持久化且不进 git |
session |
仅当前会话,进程结束失效 |
cliArg |
通过 CLI 参数生效 |
Bash 权限提示的建议目标就是 localSettings:原样回传它,规则落盘后这个工具不再打扰。
典型流程:批准后「以后都允许」
SDK 替代交互式提示后,canUseTool 的 options.suggestions 会带建议的更新:
- 调用落为「需要询问」,SDK 调
canUseTool,options.suggestions给出建议(Bash 建议localSettings) - 返回
{ behavior: 'allow', updatedPermissions: suggestions } - SDK 把规则写进对应目标,之后同样的调用直接过
实践要点
- 批准并想记住 →
addRules;收紧先前放行的规则 →replaceRules deny也能持久化:addRules加一条behavior: 'deny'的规则,把高风险工具会话内拉黑- 默认用
session或localSettings;只有确信全局适用才用userSettings;projectSettings会进 git,写入前要谨慎
下一篇:Turn——回到最基础的那个概念,把「一个来回」的边界说清楚。