claude agent sdk 指南

07-权限审批

读完这篇你能:用 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>;

在权限处理这块,语言描述不方便,我绘制了一个时序图

Clipboard_Screenshot_1787273712

请求参数解释

  • 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选项:当工作目录始终允许执行该操作。 790

其实这就是 批准某项操作后还希望”记住这次选择”,让用户不再被重复询问同一个工具。这里我们就来详细介绍一下 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 / Edit
  • ruleContent:可选。Bash 通常是允许的命令模式;省略表示该工具的所有调用

行为:PermissionBehavior

"allow" 放行 / "deny" 拒绝 / "ask" 仍询问。

写到哪:PermissionUpdateDestination

目标 生效范围
userSettings 用户全局,所有项目
projectSettings 项目级,会进 git 共享
localSettings 项目本地 .claude/settings.local.json,持久化且不进 git
session 仅当前会话,进程结束失效
cliArg 通过 CLI 参数生效

Bash 权限提示的建议目标就是 localSettings:原样回传它,规则落盘后这个工具不再打扰。

典型流程:批准后「以后都允许」

SDK 替代交互式提示后,canUseTool 的 options.suggestions 会带建议的更新:

  1. 调用落为「需要询问」,SDK 调 canUseTool,options.suggestions 给出建议(Bash 建议 localSettings)
  2. 返回 { behavior: 'allow', updatedPermissions: suggestions }
  3. SDK 把规则写进对应目标,之后同样的调用直接过

实践要点

  • 批准并想记住 → addRules;收紧先前放行的规则 → replaceRules
  • deny 也能持久化:addRules 加一条 behavior: 'deny' 的规则,把高风险工具会话内拉黑
  • 默认用 session 或 localSettings;只有确信全局适用才用 userSettings;projectSettings 会进 git,写入前要谨慎

下一篇:Turn——回到最基础的那个概念,把「一个来回」的边界说清楚。