Claude Code 的 Spinner Verbs 不是模型状态码,而是一个轻量等待设计:用随机动词降低长任务等待时的死屏感。
阅读时间:约 5 分钟
AI 编程工具最容易暴露体验问题的地方,在回答还没回来时。
终端卡在那里,模型还在读文件、调工具、推理、回写。用户不知道它是快结束了,还是陷进了某个看不见的循环。
Claude Code 在这段空白里放了一个很小的设计:进度条上不显示固定的 Thinking...,而是不断出现 Marinating...、Reticulating...、Clauding...、Flibbertigibbeting... 这类词。
它看起来像彩蛋。实际拆开以后,会发现这是一个典型的工程体验设计:机制极简单,但刚好打在用户最不舒服的等待阶段。
图:AI 编程等待状态设计
这个动词不代表模型在干什么
先把最容易误解的一点讲清楚:Marinating... 或 Reticulating... 并不表示 Claude 正在执行某个特定推理步骤。
这些词叫 Spinner Verbs。它们来自 Claude Code 源码里的默认词库。每当工具进入等待状态,UI 层从词库里抽一个动词,拼到终端进度条上。
典型显示长这样:
● Marinating... (2m 11s · ↑ 3.2k tokens)
● Clauding... (5m 03s · ↑ 8.7k tokens)
● Reticulating... (0m 47s · ↑ 912 tokens)
这里有两个真实信息:耗时和 token 量。动词本身没有状态语义。
也就是说,看到 Brewing... 不代表模型进入了"酝酿阶段";看到 Running tests... 这类自定义词,也不代表测试一定正在执行。它只是等待文案。
这个边界很重要。把它当状态码理解,会误读工具行为;把它当等待设计看,反而能看清它为什么有效。
长等待需要反馈,不只是进度
传统命令行工具的等待很短。ls、cat、grep 这类命令通常毫秒级返回,用户不需要被安抚。
AI 编程工具不同。一次复杂任务可能持续几分钟:先读十几个文件,再调用多个工具,中间还要组织上下文。屏幕上如果只剩一个固定的 Thinking...,用户很快会开始怀疑:
- 是不是卡死了
- 是不是没有读到关键文件
- 是不是应该中断重来
- 这个等待还值不值得继续
Claude Code 让等待文案持续变化,放弃了看似精确的进度伪装。
Spinner Verbs 给用户一个轻量信号:工具仍在运行,界面还活着,等待不是一块死屏。它没有提供更多技术状态,却降低了等待时的心理噪声。
这也是它比固定 Thinking... 更耐看的原因。固定文案读一次以后就消失在注意力里;随机动词会让用户偶尔扫一眼,确认系统仍在推进。
187 个词库把工具性变成了性格
社区整理过 Claude Code 的默认词库。它从早期的几十个词扩到大约 187 个,并且还会随版本变化。
这些词不是同一种口味。大致可以分成几类:
| 类型 | 示例 | 带来的感觉 |
|---|---|---|
| 思考类 | Pondering、Ruminating、Cerebrating |
像在认真琢磨 |
| 烹饪类 | Marinating、Brewing、Simmering |
像慢慢处理材料 |
| 流动类 | Flowing、Cascading、Spelunking |
像过程还在展开 |
| 创作类 | Forging、Crafting、Orchestrating |
像在组装结果 |
| 梗和彩蛋 | Reticulating、Clauding、Gitifying |
给熟悉语境的人一点会心感 |
其中最出圈的可能是 Reticulating。它是在向《模拟城市》加载界面的 "Reticulating splines" 致意。Clauding 则是把产品名变成动词,意思接近"正在以 Claude 的方式工作"。
这类词本身没有工程含义,但会让工具有一种可识别的性格。
这里的尺度也拿得很准。动词只占一行,不抢任务主体,不解释自己,不要求用户理解梗。懂的人会停一下,不懂的人也不会被打断。
实现核心只是一个动词池加随机抽样
从工程实现看,Spinner Verbs 很朴素。它主要做三件事:
- 保存默认词库
- 读取用户配置
- 从最终词库里随机抽一个词
可以把核心逻辑理解成下面这段 TypeScript:
export class SpinnerVerbs {
private static readonly DEFAULT_VERBS: string[] = [
"Accomplishing",
"Actioning",
"Baking",
"Marinating",
"Reticulating",
"Clauding",
"Flibbertigibbeting",
// ... 共约 187 个
];
private customVerbs: string[] = [];
private mode: "replace" | "append" = "append";
constructor(config?: { mode?: "replace" | "append"; verbs?: string[] }) {
if (config) {
this.mode = config.mode ?? "append";
this.customVerbs = config.verbs ?? [];
}
}
getVerbs(): string[] {
if (this.customVerbs.length === 0) {
return SpinnerVerbs.DEFAULT_VERBS;
}
if (this.mode === "replace") {
return this.customVerbs;
}
return [...SpinnerVerbs.DEFAULT_VERBS, ...this.customVerbs];
}
getRandomVerb(): string {
const verbs = this.getVerbs();
return verbs[Math.floor(Math.random() * verbs.length)];
}
}
这里没有复杂策略。没有按任务类型分类,也没有按调用阶段映射动词。
最终显示哪个词,取决于 Math.random()。简单到有点直白。
完整链路可以画成这样:
这个实现有一个好处:它不会把 UI 文案和模型内部状态绑死。等待文案可以频繁变化,模型链路不用为它增加额外状态。
自定义入口在 settings.json
如果想改这组等待动词,可以在 ~/.claude/settings.json 里加 spinnerVerbs 配置。
mode 有两个值:
| mode | 行为 | 适合场景 |
|---|---|---|
append |
在默认词库后追加自定义词 | 保留官方风格,只加少量个人偏好 |
replace |
完全替换默认词库 | 想让等待文案保持统一风格 |
一个偏工程风格的配置可以这样写:
{
"spinnerVerbs": {
"mode": "replace",
"verbs": [
"Analyzing architecture",
"Reviewing patterns",
"Parsing dependencies",
"Checking types",
"Running tests",
"Generating code"
]
}
}
一个更轻松的配置也能工作:
{
"spinnerVerbs": {
"mode": "append",
"verbs": [
"Caffeinating",
"Yirgacheffe-brewing",
"Single-origining"
]
}
}
配置链路很短:
~/.claude/settings.json
-> 读取 spinnerVerbs 字段
-> 传入 SpinnerVerbs 构造函数
-> 合并或替换词库
-> 随机抽词
-> 渲染到终端
还有几个实现细节值得记住:
- Claude Code 会热加载
settings.json,改完保存后通常不用重启 - 项目级
.claude/settings.json会覆盖全局~/.claude/settings.json - 词条没有严格长度限制,但建议控制在 100 个字符以内
- 词库不会自动去重;
append模式下重复词会提高被抽中的概率 - JSON 写错会导致配置加载失败,界面会回退到默认词库
- 自定义能力需要 Claude Code v2.1.23(2026 年 1 月)或更高版本
小功能的价值在边界感
Spinner Verbs 好用,原因在于克制。
它没有假装自己能展示真实推理阶段,也没有把等待界面做成信息密度很高的仪表盘。它只是承认一件事:AI 编程里的等待很长,而且等待过程中用户需要知道系统还在动。
这种设计对开发工具尤其有参考价值。
很多体验优化不需要侵入核心链路。一个随机动词池、一个可覆盖的配置项、一次轻量渲染,就能让工具从"正在执行命令"变成"正在陪用户度过一段不确定的等待"。
工程上看,它是一个不到百行的 UI 小模块。产品上看,它让 Claude Code 有了自己的语气。
推荐阅读
DeepSeek Harness 的 Agent 运行时设计