# Claude Code Hooks、Skills 与斜杠命令：区别、选型和最小配置

> 需要复用审查步骤，先写 skill；需要每次工具调用前检查一个具体条件，配置 hook。斜杠命令是调用入口，自定义 commands 已并入 skills，技能既能由人调用，也能由 Claude 按需加载。

- URL: https://blog.laozhang.ai/zh/posts/claude-code-hooks-slash-commands-skills
- Published: 2026-06-01
- Updated: 2026-10-05
- Author: LaoZhang AI Team (https://blog.laozhang.ai/zh/about)
- Topic: Claude Code
- Tags: Claude Code, Hooks, 斜杠命令, Skills, 开发工作流

---
选择 Claude Code 的 hooks、skills 和斜杠命令，可以先看你要完成什么：**复用一套审查、写作或迁移方法，用 skill；在指定事件发生时执行检查，用 hook；控制启动时机或会话设置，用对应的 `/` 入口。** 它们可以配合，但不是三个互斥的功能。

截至 2026 年 10 月 4 日，官方文档已明确：自定义 commands 合并到了 skills。`.claude/commands/review-notes.md` 和 `.claude/skills/review-notes/SKILL.md` 都能提供 `/review-notes`，旧命令文件仍可使用。Skill 默认允许用户直接调用，也允许 Claude 在相关任务中加载，因此“命令手动、技能自动”不能作为通用定义。[官方 Skills 文档](https://code.claude.com/docs/en/skills)

本文先帮你选机制，再给出一套变更审查 skill 和一个保护 `.env` 文件的最小 hook。代码示例的 JSON、YAML 和 Python 分支做过离线检查；没有在 Claude Code 中安装、调用或验证模型输出。

## 先区分斜杠入口背后执行的是什么

输入 `/` 是一个操作入口，入口背后可能是固定控制逻辑，也可能是一套交给模型执行的指令。看到斜杠，不能直接判断是否调用模型、是否消耗 token，或是否修改文件。

| 类型 | 做什么 | 典型例子 | 怎样确认 |
| --- | --- | --- | --- |
| 内置控制命令 | 大多数直接执行固定逻辑，管理会话或设置 | `/help`、`/permissions`、`/skills` | 看当前会话菜单及官方命令参考 |
| 捆绑技能 | Claude Code 随产品提供的提示词流程，由 Claude 使用工具执行 | `/code-review`、`/debug`、`/claude-api` | 官方命令表在用途列标为 Skill；可用性受版本和功能影响 |
| 自定义技能或旧命令文件 | 你维护的说明、步骤和资料，通过 `/name` 或模型加载使用 | `/review-notes` | 看技能文件、调用控制字段和 `/skills` |
| 事件钩子（hook） | 运行时到达指定事件后，执行匹配的处理器 | `PreToolUse` 的文件检查 | 用 `/hooks` 检查事件、匹配条件和处理器 |

这是当前 [命令参考](https://code.claude.com/docs/en/commands) 与 [捆绑技能说明](https://code.claude.com/docs/en/skills#bundled-skills) 的区分。不同版本、平台和已启用功能可能提供不同入口，先检查自己的会话菜单，再照例子操作。

例如，想让 Claude 协助 Claude API 项目，可以输入 `/claude-api`；已有 API 代码需要迁移时，可用 `/claude-api migrate`。它是捆绑技能，会加载对应语言的参考资料；代码导入 `anthropic` 或 `@anthropic-ai/sdk` 时也可能自动激活。它不是“任何 API 设置都由一个内置命令完成”，更不代表无需凭证或可以直接发布项目。[Claude API 技能说明](https://code.claude.com/docs/en/skills#work-on-claude-api-projects)

## 按具体任务选机制，先保留最小流程

| 现在的需求 | 先选什么 | 选择理由与下一步 |
| --- | --- | --- |
| 每次审查都想使用同一套证据和报告格式 | 自定义 skill | 把方法写进 `SKILL.md`，先用 `/review-notes 文件路径` 显式试用 |
| 发布检查需要你决定何时开始 | 手动调用的 skill | 设置 `disable-model-invocation: true`；把发布授权与检查步骤分别写清 |
| 每次 `Edit` 或 `Write` 前都要检查目标是否为 `.env` | `PreToolUse` command hook | 用事件和工具名定位，再在脚本里检查路径；下面提供可离线验证的例子 |
| 编辑完成后需要格式化 | `PostToolUse` hook | 动作已经发生，适合后处理；格式化失败不能撤销刚才的编辑 |
| 希望停止前由模型判断任务是否完成 | 支持该事件的 prompt 或 agent hook | 可以做判断，但要接受模型误判、成本与延迟，并设计无法完成时的退出方式 |
| Claude 缺少远程 PR、当前文档或浏览器访问 | 现有工具或 MCP 集成 | Skill 教方法，不能凭空提供外部访问权限 |

最后一种需求可以继续看 [按工作流选择 Claude Code MCP](https://blog.laozhang.ai/zh/posts/claude-code-best-mcp-servers)，先判断缺的是仓库、文档、浏览器还是观测数据。本篇只处理技能与钩子的分工。

![按调用入口、可复用方法和事件处理区分命令、Skill 与 Hook](https://blog.laozhang.ai/posts/zh/claude-code-hooks-slash-commands-skills/img/invocation-method-event.webp)

文件位置可以帮助维护，但不决定全部行为。一个 skill 也可以由人通过斜杠调用；hook 也可以调用模型做判断。真正影响选择的是：由谁启动、执行什么、失败时能改变哪一步。

## 写一个可手动调用的变更审查 skill

如果你反复粘贴同一份审查要求，先把它变成一个 skill，比同时增加命令包装、hook 和多个脚本更容易检查。下面的例子适合“阅读某个现有文件，输出风险和验证建议”，不负责执行修改或发布。

前提是已经安装并启动 Claude Code，项目中有待审查文件。入口文件名必须是 **`SKILL.md`**，区分大小写。以项目级技能为例，文件放在 `.claude/skills/review-notes/SKILL.md`，内容如下：

```markdown
---
name: review-notes
description: 阅读指定代码文件，给出有位置和证据的变更审查意见
argument-hint: "[项目内文件路径]"
disable-model-invocation: true
---

审查用户指定的文件：$ARGUMENTS。

1. 如果没有参数，询问待审查文件路径，然后停止本次审查。
2. 读取指定文件及理解其行为所需的直接依赖；文件不存在时说明问题，不猜测内容。
3. 按 [检查清单](checklist.md) 检查行为。
4. 只报告能够指出文件位置和具体触发条件的问题；把不确定项标成待验证。
5. 输出“发现、依据、建议验证”三栏；未发现明确问题时说明检查范围。
6. 本流程只阅读和提出建议，不编辑文件、不提交、不发布。
```

同目录的 `checklist.md` 内容为：

```markdown
# 变更审查清单

- 输入为空或格式错误时，代码走向哪里？
- 外部调用失败后，是否向调用者保留可识别的错误？
- 状态更新是否可能发生在验证之前？
- 哪个具体输入或已有测试可以验证发现？
```

文件中的 YAML frontmatter 必须从第一行的 `---` 开始，字段名需准确。YAML 解析失败或字段拼写错误时，技能可能仍加载，但预期的调用控制不会生效；不要只靠菜单出现来判断配置正确。

`$ARGUMENTS` 会替换为技能名后的参数。例如项目中确实存在 `src/auth.ts` 时，在 Claude Code 提示框输入：

```text
/review-notes src/auth.ts
```

成功的信号是：`/skills` 中能够找到 `review-notes`，调用后 Claude 阅读目标文件，输出带位置或检查范围的意见。菜单出现只说明技能被发现，不能证明审查质量；再核对回答是否遵循清单、是否把猜测当成事实。没有参数、路径不存在或依赖无法读取时，应得到明确说明，而不是一份假审查报告。

新建技能目录后若没有被发现，可以使用 `/reload-skills` 重新扫描。当前文档说明技能目录的文件变化通常会在会话中被发现；如果会话开始时顶层 skills 目录尚不存在，则需要重新扫描。[创建与配置技能](https://code.claude.com/docs/en/skills#getting-started)、[支持文件](https://code.claude.com/docs/en/skills#add-supporting-files)

辅助文件按需读取，让入口保持简洁；没有额外资料的小 skill 也可以成立。它的价值在于可复用，不取决于必须配几个脚本。旧 `.claude/commands/review-notes.md` 若已经能完成同样任务，不需要为了目录变化立即迁移；需要同目录清单、脚本或其他支持文件时，再采用技能目录更方便。

## 调用开关与工具权限要分开理解

上面的 `disable-model-invocation: true` 是调用控制：让你决定何时开始。正文中的“不编辑文件”是流程要求，两者都不能替代工具权限策略。

| 字段设置 | 用户能否输入 `/name` | Claude 能否按相关性调用 | 适合什么 |
| --- | --- | --- | --- |
| 两个字段都用默认值 | 能 | 能 | 可直接调用，也可按需加载的通用方法 |
| `disable-model-invocation: true` | 能 | 不能 | 由人控制启动时机的流程 |
| `user-invocable: false` | 不能 | 能 | 供模型加载的背景知识 |

`user-invocable: false` 不只是从菜单隐藏，直接输入 `/name` 也不会运行。`disable-model-invocation: true` 则同时避免把描述加入模型用于自动选择的技能列表。配置前先决定要限制哪一方，别把两个开关都设上却仍期待正常调用。[调用控制说明](https://code.claude.com/docs/en/skills#control-who-invokes-a-skill)

另一个容易误用的字段是 `allowed-tools`。例如 `allowed-tools: Read Grep` **预先批准列出的工具，不会把其他工具从可用列表移除**。未列出的工具仍受正常权限设置约束；不能因此宣称整个技能已被强制限制为只读。若要移除工具，应理解 `disallowed-tools`；需要长期禁止某类工具，则使用权限设置中的 deny 规则。[技能工具预授权](https://code.claude.com/docs/en/skills#pre-approve-tools-for-a-skill)

这些临时控制还有明确期限：技能正文在调用后会留在对话中，跨后续轮次保留；`allowed-tools` 的授权及 `disallowed-tools` 的工具移除会在下一条用户消息时清除。内容仍在，不代表授权也仍在。组织启用 `allowManagedPermissionRulesOnly` 时，受影响来源的 `allowed-tools` 还会被忽略，适用行为要求 v2.1.282 或更高版本，不应通过改配置绕开组织规则。[内容生命周期](https://code.claude.com/docs/en/skills#skill-content-lifecycle)、[托管权限规则](https://code.claude.com/docs/en/skills#when-only-managed-permission-rules-apply)

## Hook 的触发由事件决定，判断方式有五种

Hook 由事件、可选匹配条件和处理器组成。例如 `PreToolUse` 表示工具调用之前，`Write|Edit` 匹配工具名，`command` 指定执行脚本。配置生效且事件匹配时，运行时启动处理器，无需模型先决定“我要不要加载这个 skill”。

当前官方文档列出了五种处理器，支持的事件组合仍需查对应事件说明：

| `type` | 如何执行 | 适合的工作 | 需要考虑什么 |
| --- | --- | --- | --- |
| `command` | 执行 shell 命令，事件 JSON 通过标准输入传入 | 路径检查、固定规则、格式化 | 脚本依赖、退出码、超时与副作用 |
| `http` | 将事件 JSON POST 到 URL | 由已有服务处理校验或记录 | 数据去向、服务可用性、响应协议 |
| `mcp_tool` | 调用已配置 MCP 服务器的工具 | 使用现有集成执行事件动作 | 服务器与工具权限、事件支持范围 |
| `prompt` | 单轮模型评估 | 根据事件输入作有限判断 | 模型误判、token 成本与等待时间 |
| `agent` | 多轮验证，可使用工具 | 需要读文件或执行检查的验证 | 实验性功能、成本、时限及执行范围 |

因此，**事件触发确定，不等于判断结果确定**。一个纯脚本可以按明确条件稳定返回结果；prompt 和 agent hook 仍包含模型判断。官方支持它们，不能笼统说“hooks 不允许主观判断”，也不能把自动触发解释成质量保证。[Hook 工作方式](https://code.claude.com/docs/en/hooks-guide#how-hooks-work)、[Prompt hooks](https://code.claude.com/docs/en/hooks-guide#prompt-based-hooks)

例如 `Stop` hook 判断任务未完成时，可以阻止停止、让 Claude 继续工作。但如果成功条件永远无法满足，就可能反复检查和继续执行。使用判断型 hook 前，应定义可检查的条件、超时和无法完成时的处理方式；需要你决定什么时候开始的复杂审查，仍可保留为手动 skill。

## 配置一个仅保护 `.env` 写入的 hook

下面的例子只检查 Claude Code 的 `Write` 和 `Edit` 工具，阻止写入文件名为 `.env` 或以 `.env.` 开头的文件，包含 `.env.example`。它不会读取文件内容，也不会发送网络请求。需要 macOS 或 Linux、可用的 Python 3，以及一个专门用于试验的项目。

将下面脚本保存为项目根目录下的 `scripts/guard-env.py`。它通过 Python 标准库读取输入，不需要安装第三方包，也不需要可执行权限，因为配置会显式调用 `python3`。

```python
import json
import sys
from pathlib import PurePosixPath


def block(reason):
    print(reason, file=sys.stderr)
    return 2


def main():
    try:
        event = json.load(sys.stdin)
    except (json.JSONDecodeError, UnicodeDecodeError):
        return block("无法解析 hook 输入；请检查事件 JSON。")

    if not isinstance(event, dict):
        return block("hook 输入必须是 JSON 对象。")
    if event.get("hook_event_name") != "PreToolUse":
        return 0
    if event.get("tool_name") not in ("Write", "Edit"):
        return 0

    tool_input = event.get("tool_input")
    if not isinstance(tool_input, dict):
        return block("Write/Edit 输入缺少 tool_input 对象。")
    file_path = tool_input.get("file_path")
    if not isinstance(file_path, str) or not file_path:
        return block("Write/Edit 输入缺少有效的 file_path。")

    name = PurePosixPath(file_path.replace("\\", "/")).name
    if name == ".env" or name.startswith(".env."):
        return block("已阻止 Write/Edit 修改环境文件，请由维护者处理。")
    return 0


if __name__ == "__main__":
    sys.exit(main())
```

再把下面 `hooks` 配置合并进该测试项目的 `.claude/settings.json`。如果已有其他设置或 hook，要合并对应字段和数组，不要用这份对象覆盖整个文件。`CLAUDE_PROJECT_DIR` 由 Claude Code 提供；路径加引号可处理项目名中的空格。

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/guard-env.py\"",
            "timeout": 10
          }
        ]
      }
    ]
  }
}
```

个人配置 `~/.claude/settings.json` 会影响多个项目，项目配置 `.claude/settings.json` 用于共享，本地项目配置 `.claude/settings.local.json` 用于个人设置。先选择范围再配置；`/hooks` 可查看已注册的事件与处理器，直接编辑配置后文件监听器通常会自动加载，未检测到变化时再重启会话。[配置位置](https://code.claude.com/docs/en/hooks#hook-locations)

### 先离线检查，再确认实际触发

在脚本所在项目根目录执行下面的输入模拟，不会创建或修改 `.env`：

```bash
printf '%s\n' '{"hook_event_name":"PreToolUse","tool_name":"Write","tool_input":{"file_path":"/tmp/hook-demo/.env","content":"demo"}}' | python3 scripts/guard-env.py
printf '退出码：%s\n' "$?"
```

预期标准错误显示阻止原因，退出码为 `2`。再把路径换成 `/tmp/hook-demo/notes.txt` 重试，预期没有输出，退出码为 `0`。本次离线检查还覆盖了 Windows 风格的 `.env.local` 路径、非目标工具、错误事件、缺失路径和无效 JSON；结果符合脚本定义。它只证明解析和分支行为，不证明 Claude Code 已发现配置或真正触发了事件。

实际接入时，在测试项目的 `/hooks` 中确认 `PreToolUse`、`Write|Edit` 和脚本命令都已注册，再用无敏感内容的测试路径让 Claude 尝试 `Write` 或 `Edit`。成功判据是环境文件写入在发生前被阻止，并显示原因；普通文件进入正常权限流程。用 Bash 写文件不会触发这里的 `Write|Edit` 匹配，不能用那个结果判断此示例失效。

这个例子是窄规则，不是完整文件安全边界。它不覆盖 Bash、PowerShell、其他工具或文件名别名，也不能保证其他处理器没有副作用。需要更广的访问约束时，结合工具权限和操作系统隔离处理，别把一个文件名检查扩称为防泄露系统。[PreToolUse 输入和决策](https://code.claude.com/docs/en/hooks#pretooluse)

### 退出码的含义随事件变化

脚本使用一套简单协议：不输出 JSON，靠退出码和标准错误反馈。对这个 `PreToolUse` 例子，`0` 表示没有异议，**不等于批准工具调用**；`2` 会阻止这次工具调用，标准错误给出原因。

不能把这个规则直接套在所有事件上：

| 事件 | 返回 `2` 会怎样 |
| --- | --- |
| `PreToolUse` | 工具尚未执行，阻止工具调用 |
| `PostToolUse` | 将标准错误反馈给 Claude；工具已经执行，无法撤销 |
| `Stop` | 阻止停止，让对话继续 |
| `SessionStart` | 向用户显示标准错误，会话仍继续 |

其他非零退出码通常是非阻断错误，但还要看结构化 JSON 是否有效，以及事件自身的例外。例如 `WorktreeCreate` 对任何非零退出码都按失败处理。需要 `allow`、`ask` 等结构化决策时，应改用对应事件的 JSON 输出协议，不要混用两套协议后凭退出码猜结果。[退出码参考及事件表](https://code.claude.com/docs/en/hooks#exit-code-output)

## 组合时分清职责，也分清什么时候失效

变更审查和文件保护可以一起使用：你输入 `/review-notes src/auth.ts` 启动审查，skill 提供方法；项目级 hook 在任何匹配的写入前检查 `.env` 文件名。审查建议需要解释证据，路径规则只需返回通过或阻止，职责清楚就容易定位问题。

![审查 Skill、写入前路径 Hook 与 Stop 模型判断的工作流示意](https://blog.laozhang.ai/posts/zh/claude-code-hooks-slash-commands-skills/img/review-hook-composition.webp)

同一事件匹配的 hooks 会并行执行，所有处理器完成后再合并结果。一个 hook 拒绝动作，不会阻止另一个 hook 先完成日志记录或其他副作用；`PreToolUse` 权限决策按最严格结果合并。异步处理器完成时也不能追溯阻止已经执行的动作。[多个 hooks 的结果合并](https://code.claude.com/docs/en/hooks-guide#combine-results-from-multiple-hooks)

还有三个容易混淆的期限：

- **技能正文**：调用后进入对话，跨轮次保留；上下文压缩时仍受保留预算影响。
- **技能工具授权或移除**：`allowed-tools` 和 `disallowed-tools` 在下一条用户消息时清除。
- **技能 frontmatter 中的 hooks**：调用技能时注册，通常保留到会话结束，不会在该技能当前轮次结束时自动卸载。`once: true` 可在首次成功执行后移除；阻断、失败或超时不算成功。子代理自己的 hooks 则只在该子代理运行期间有效。

这些是不同机制，不能用“skill 已结束”推断其 hook 已经停止。上面的 `.env` 例子放在项目设置中，不属于技能临时授权。[技能与子代理中的 hooks](https://code.claude.com/docs/en/hooks#hooks-in-skills-and-agents)

维护时留下可读的说明：谁启动、检查什么、失败时怎么处理、允许什么副作用、什么时候删除。若一个斜杠入口只重复调用同一个 skill，通常没必要再维护一份提示词；若一个 hook 越来越像长篇审查方法，考虑把方法保留在 skill，hook 只负责需要自动发生的事件动作。

## 常见问题

### 自定义 slash commands 还要迁移吗？

不必全部迁移。官方明确旧 `.claude/commands/*.md` 继续工作，并已与 skills 合并。需要同目录支持文件或更完整的技能组织时，再用 `.claude/skills/review-notes/SKILL.md`；入口文件名必须准确。[官方兼容说明](https://code.claude.com/docs/en/skills)

### Skills 只能由 Claude 自动触发吗？

不是。默认既可由用户输入 `/name`，也可由 Claude 按需加载。`disable-model-invocation: true` 只保留用户调用；`user-invocable: false` 只保留模型调用，直接输入也不会运行。[调用控制](https://code.claude.com/docs/en/skills#control-who-invokes-a-skill)

### `allowed-tools: Read` 能保证 skill 只读吗？

不能。它预先批准 Read，不移除其他工具。要区分技能正文中的行为要求、临时工具移除和长期权限 deny 规则，不能把预授权当作工具白名单。[工具权限说明](https://code.claude.com/docs/en/skills#pre-approve-tools-for-a-skill)

### Hooks 能保证每次判断都正确吗？

不能。事件匹配控制的是何时执行，模型型处理器仍可能误判，脚本也可能规则不完整。能明确检查的条件优先用简单脚本；需要模型判断时，为失败、超时和无法满足的条件设计处理方式。[处理器类型](https://code.claude.com/docs/en/hooks-guide#how-hooks-work)

### Hook 出错后，刚才的文件修改会撤销吗？

不会因为 `PostToolUse` 出错就自动撤销。要在工具执行前阻止，应使用支持该决策的 `PreToolUse`；工具已经运行时，后置 hook 只能按该事件的协议反馈或控制后续流程。[事件与退出码](https://code.claude.com/docs/en/hooks#exit-code-2-behavior-per-event)

先把一个具体任务做通：可复用步骤成为 skill，启动时机由合适的调用入口控制，事件规则由 hook 执行。配置发现、脚本分支和实际触发分别检查，才知道问题出在入口、方法、权限还是事件上。

## 参考来源

本文引用的外部页面，按正文出现顺序排列。最后更新于 2026-10-05。

- [官方 Skills 文档](https://code.claude.com/docs/en/skills) (code.claude.com)
- [命令参考](https://code.claude.com/docs/en/commands) (code.claude.com)
- [Hook 工作方式](https://code.claude.com/docs/en/hooks-guide) (code.claude.com)
- [配置位置](https://code.claude.com/docs/en/hooks) (code.claude.com)
