您输入 /fix-issue 123,Claude Code 会将其扩展为包含团队标准的完整提示。自定义斜杠命令是 Claude Code 中成本最低的自动化方式——只需一个 markdown 文件,零配置——但它们已经以一种容易被忽视的方式发生了变化:自定义命令已合并到技能中。您的 .claude/commands/ 文件仍然有效,但合并后的模型解释了几个看起来像 bug 的现象(参数未展开、工具授权在任务中途消失),并添加了命令从未拥有的功能。
以下是当前的行为,截至 2026 年 7 月下旬已通过官方文档验证。
单文件版本仍然有效
将 markdown 文件放入您的项目:
<!-- .claude/commands/fix-issue.md -->
---
description: "Fix a GitHub issue by number"
---
Fix GitHub issue $ARGUMENTS following our coding standards.
Read the issue with `gh issue view`, locate the relevant code,
implement the fix, and add a regression test.
Enter fullscreen mode Exit fullscreen mode
输入 /fix-issue 123 会发送该内容,并将 $ARGUMENTS 替换为 123。
合并意味着此文件和位于 .claude/skills/fix-issue/SKILL.md 的技能都会创建 /fix-issue 且行为相同。技能额外提供的内容:
- 一个用于支持文件的目录(模板、脚本、参考文档,Claude 仅在需要时加载)
- frontmatter 用于控制谁可以调用它——您、Claude 或两者
- 自动调用:当您的对话与描述匹配时,Claude 可以加载它
如果命令和技能同名,技能优先。我的实际规则:临时个人快捷方式可以留在 commands/ 中;任何您要扩展或分享的内容都应放在 skills/ 中。(如何编写实际能触发的描述是另一个话题——我在 SKILL.md 指南 中有介绍。)
参数:$ARGUMENTS、$0 及引用规则
三条展开规则涵盖了我看到的大多数困惑:
1. $ARGUMENTS 是输入的完整字符串。 /fix-issue 123 high-priority → $ARGUMENTS 变为 123 high-priority。
2. 索引访问从零开始且按 shell 引用。 $ARGUMENTS[0](或简写 $0)是第一个参数。引用会分组单词:
/migrate-component "search bar" React Vue
→ $0 = search bar, $1 = React, $2 = Vue
Enter fullscreen mode Exit fullscreen mode
3. 缺失值根据占位符类型有不同表现。 没有匹配参数的索引占位符(当您只传递一个参数时的 $2)会在文本中保持不变。frontmatter 中声明的命名占位符会扩展为空字符串。如果您需要在散文中使用字面上的美元数字($1.00),请转义:\$1.00。
另一个安全网:如果您传递了参数但文件不包含任何 $ARGUMENTS,Claude Code 会在末尾附加 ARGUMENTS: <your input>,因此您的输入永远不会被静默丢弃。
实时数据注入:!`command`
这是将预设提示转变为基于实际数据的功能。类似这样的行:
## Current diff
!`git diff HEAD`
Enter fullscreen mode Exit fullscreen mode
会在 Claude 看到任何内容之前运行 shell 命令,并用输出替换占位符。Claude 接收到实际的 diff,而不是去获取它的指令——减少一次工具往返,也不会出现模型"总结"它从未读取过的 diff 的情况。
替换是单次进行的:命令的输出不能再发出另一个 !`…` 占位符进行第二次展开。请将其视为数据,而非宏。
allowed-tools 是一次性授权,而非会话设置
合并模型中最常见的意外。类似这样的 frontmatter:
allowed-tools: Bash(git add:*), Bash(git commit:*)
Enter fullscreen mode Exit fullscreen mode
会预先批准这些工具,仅限调用技能的这一轮。当您发送下一条消息时,授权会清除——即使技能的内容仍保留在上下文中。所以"为什么 Claude 又在请求同一命令的权限?"通常是因为:指令持久化了,但授权没有。重新调用技能会重新应用它。
两个相关说明:
allowed-tools不会限制任何内容。未列出的工具在您正常的权限设置下仍然可用。${CLAUDE_SKILL_DIR}会在正文和allowed-tools中展开,因此技能可以附带脚本并精确预批准该脚本的调用——无需提示,无需通配符。
如果您想要一个在整个会话中都有效的授权,那应该放在您的权限规则中——allow/deny/ask 匹配逻辑是另一个雷区。
决定谁可以调用它
默认情况下,您和 Claude 都可以运行任何技能。两个 frontmatter 开关可以改变这一点:
disable-model-invocation: true—— 只有您可以触发它。用于具有副作用且时机重要的任何操作:/deploy、/commit、/send-release-notes。您不希望模型因为代码"看起来准备好了"就进行部署。user-invocable: false—— 只有 Claude 可以加载它。用于不是有意义操作的背景知识,如legacy-system-context技能。
技能的描述始终在上下文中,因此 Claude 知道存在什么;正文在调用时加载。
正文会持久保留——将其写成常设指令
一旦调用,渲染后的内容会在会话的剩余时间内保留在对话中(重新调用相同的技能会添加一条简短的"已加载"提示,而不是重复)。两个后果:
- 每一行都是重复的 token 成本。说明要做什么;跳过解释原因的长篇大论。
- 将指令写成常设规则("编辑 Y 后始终运行 X"),而不是一次性步骤——Claude 不会在后续回合重新读取文件。
上下文压缩后,最近的技能会在固定 token 预算内重新附加,因此长会话可能会静默丢弃较旧的技能——详情见压缩后保留的内容。
何时斜杠命令不是正确的工具
- 一个事实Claude 应该始终知道 → CLAUDE.md 中的一行。
- 一个您一直在粘贴的过程 → 一个技能。这是最合适的选择。
- 必须每次确定性地发生的事情 → 一个钩子。技能依赖模型选择遵守;钩子不需要请求。
- 太大无法放入上下文的参考材料 → 带支持文件的技能,从 SKILL.md 引用,按需加载。
快速陷阱清单
$0是第一个参数(从零开始),引号会分组单词- 未匹配的
$N保持字面值;未匹配的命名参数变为空 !`command`在 Claude 读取任何内容之前运行;单次通过allowed-tools在您发送下一条消息时清除- 与捆绑技能同名的项目技能(
code-review)会替换它 .claude/commands/仍然有效;技能是推荐路径
我维护 Rulestack —— 为 Claude Code、Cursor 和 Codex 提供版本化规则和技能包,与此类变更保持同步:https://rulestack.gumroad.com?ref=devto
关于 AI 编码工作流的每日笔记在 Bluesky 上发布:https://bsky.app/profile/ai-shop.bsky.social
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.