1. 从 Spinner 转圈说起:Claude Code 卡顿到底卡在哪一层
用 Claude Code 的人大概率都遇到过这个画面:终端里那个小小的 Spinner 一直在转,转了几秒、十几秒,甚至干脆停在那儿不动了。你盯着屏幕,不确定它是在思考、在等网络、还是已经死掉了。这个体验非常折磨人,因为 Claude Code 本质上是一个"对话式编程代理",它的工作模式是"读代码 → 推理 → 调工具 → 再推理",中间任何一个环节卡住,前端表现都是同一个 Spinner 在转。
所以排查卡顿的第一步,不是急着去改配置,而是先搞清楚 Spinner 这个状态标识到底在表达什么。Spinner 本身只是一个 UI 层的"进行中"指示器,它不区分"正在请求模型""正在执行本地命令""正在等待工具返回"这些不同阶段。这就导致一个很现实的问题:同样是转圈,背后的原因可能天差地别,而你如果只盯着"它卡了"这个表象,排查方向会完全跑偏。
我自己的经验是,把 Claude Code 的运行链路拆成四层来看,卡顿基本跑不出这四层:
- 输入层:你的终端、Shell 环境、粘贴的大段代码、超长上下文。
- 模型层:请求发出去之后,模型侧的排队、限流、响应生成。
- 工具层:Claude Code 调用本地命令(读文件、跑测试、执行 git 等)时的执行与等待。
- 渲染层:终端 UI 刷新、Spinner 动画、输出流式打印。
这四层里,真正"卡死"的概率其实不高,绝大多数情况是某一层变慢了,而 Spinner 没有把这个"慢"和"死"区分开。理解了这一点,后面的排查才有章法。这篇文章就是把这四层逐一拆开,告诉你每一层卡顿的典型症状、根因和对应的处理办法,尽量做到你照着做就能定位问题。
2. Spinner 状态标识的语义边界:它告诉你什么,又瞒了你什么
2.1 Spinner 只代表"进程还活着",不代表"任务在推进"
很多人对 Spinner 有个误解,觉得它转就说明"正在干活"。严格来说,Spinner 只能证明一件事:主进程的事件循环还在跑。它转,说明 UI 线程没被完全阻塞;它不转,说明连 UI 刷新都停了。但"事件循环在跑"和"任务在推进"是两码事。
举个典型场景:Claude Code 发起了一次模型请求,然后进入等待。这个等待期间,Spinner 会一直转,因为事件循环在等 IO。但如果模型侧因为限流或者排队,迟迟不返回,Spinner 就会一直转下去,看起来"很努力",实际上什么都没发生。这就是为什么你会觉得"它转了半天也没输出"。
所以正确的读法是:Spinner 转 = 进程没崩;Spinner 转很久没输出 = 大概率卡在等待某个外部响应。把这两件事分开,你的判断就准了。
2.2 不同阶段的 Spinner 表现差异
虽然 Spinner 本身不区分阶段,但结合上下文,你还是能看出一些端倪。我总结了几种常见表现和它们大概率对应的阶段:
| Spinner 表现 | 大概率所处阶段 | 常见原因 |
|---|---|---|
| 刚发消息就转,很快出字 | 模型层正常 | 无 |
| 转很久才出第一个字 | 模型层排队/限流 | 请求量大、账号限流 |
| 出字中途突然停转 | 工具层执行中 | 本地命令卡住、等待输入 |
| 转但完全无输出且时间极长 | 网络层或模型层 | 连接超时、响应丢失 |
| Spinner 动画本身卡顿、掉帧 | 渲染层 | 终端性能、输出量过大 |
这张表不是绝对的,但它能帮你快速缩小范围。比如"出字中途突然停",基本可以锁定是工具层——Claude Code 正在跑某个本地命令,而那个命令卡住了(比如等一个交互式输入,或者跑了一个死循环的测试)。
2.3 为什么 Claude Code 特别容易让人误判卡顿
Claude Code 和普通 CLI 工具不一样,它是一个"多轮工具调用"的代理。一次用户输入,背后可能是:读 5 个文件 → 跑 1 次搜索 → 执行 1 条命令 → 再读 2 个文件 → 生成回答。这中间每一步都可能触发 Spinner,而用户看到的只是"一直在转"。
更麻烦的是,工具调用是串行的(大多数情况下),前一个工具不返回,后一个就不会开始。所以如果某个本地命令卡住,整个链路就停在那儿,Spinner 还在转,但实际上是"假活"。这就是为什么排查 Claude Code 卡顿,工具层往往是重灾区。
3. 模型层卡顿:请求发出去了,但响应迟迟不来
3.1 限流与排队:最常见的"转圈元凶"
模型层卡顿里,排第一的永远是限流和排队。你发一个请求,服务端可能因为当前负载高、你的账号触发了速率限制、或者单纯就是排队,导致响应延迟。这种情况下 Spinner 会一直转,直到超时或者最终返回。
判断方法很简单:看是不是所有请求都慢,还是只有大请求慢。如果连"你好"这种一句话都要转很久,那基本是账号或网络层面的限流;如果只有长上下文、大文件分析的请求慢,那可能是请求体太大导致的处理时间增加。
应对上,我一般这么做:
- 缩短单次上下文,别一次性把整个仓库塞进去,用
@精确引用文件。 - 把大任务拆成小步骤,让每一轮的工具调用和推理都轻量一些。
- 如果怀疑是限流,隔几分钟再试,或者换个时间段。
3.2 上下文膨胀:请求体越大,首字延迟越高
Claude Code 的一个特点是它会自动把相关文件、历史对话、工具结果都塞进上下文。上下文越大,模型处理首字的时间(TTFT)就越长。这不是 bug,是物理规律——输入 token 越多,prefill 阶段越慢。
我实测过一个对比:同样问一个函数的作用,只引用单个文件时首字大概 1-2 秒;如果把整个模块十几个文件都带上,首字延迟能到 8-10 秒。这期间 Spinner 一直在转,你会以为卡了,其实是在 prefill。
所以如果你发现"越用越慢",先检查上下文是不是膨胀了。Claude Code 一般有/clear之类的命令清理会话,长会话该清就清,别舍不得。
3.3 网络链路:被忽略的中间环节
模型请求要经过网络,网络抖动、DNS 解析慢、连接复用失效,都会表现为"转圈"。这类问题的特征是偶发性:有时候快有时候慢,没有规律。
排查网络层,我习惯用最朴素的办法:在另一个终端里持续 ping 或者用 curl 测一下到服务端的连通性和延迟。如果延迟忽高忽低,那卡顿大概率是网络问题,而不是 Claude Code 本身。这种情况你改配置没用,得从网络环境入手。
注意:网络层排查时,关注的是延迟稳定性和丢包,而不是绝对速度。稳定的 200ms 比忽高忽低的 50ms 体验好得多。
4. 工具层卡顿:本地命令把整个链路拖死了
4.1 交互式命令:最隐蔽的"假死"
工具层卡顿里,最坑的是交互式命令。Claude Code 执行本地命令时,如果那个命令需要用户输入(比如git commit打开编辑器、某些脚本等待确认、npm init等待填写),而 Claude Code 又没有正确处理 stdin,命令就会一直挂着等输入。这时候 Spinner 在转,但实际上是死等。
我踩过这个坑:让 Claude Code 帮我提交代码,它跑了git commit没带-m,结果打开了编辑器,整个会话就卡在那儿了。解决办法是尽量让 Claude Code 执行非交互式命令,比如git commit -m "xxx"、npm install --yes、带-y或--non-interactive参数的命令。
如果你不确定某个命令会不会交互,可以先在终端里手动跑一遍,确认它是非交互的,再让 Claude Code 去执行。
4.2 长耗时命令:测试、构建、安装
第二类工具层卡顿是长耗时命令。跑全量测试、构建整个项目、安装依赖,这些命令本身就要几分钟甚至更久。Claude Code 会等它跑完,Spinner 一直转。这不算 bug,但体验上就是"卡"。
我的做法是:
- 让 Claude Code 跑针对性的命令,比如只跑单个测试文件,而不是全量测试。
- 构建和安装这类重活,自己在终端里跑,跑完再让 Claude Code 看结果。
- 如果必须让它跑长命令,心里有个预期时间,别误判成卡死。
4.3 命令输出过大:把终端刷爆
还有一个容易被忽略的点:命令输出过大。如果 Claude Code 执行了一个输出几万行的命令(比如find /、cat一个大日志),输出会疯狂刷屏,终端渲染压力剧增,Spinner 动画本身都会卡顿掉帧。这时候看起来是"UI 卡了",实际是渲染层被输出量压垮了。
处理办法是给命令加过滤,比如| head -100、| grep xxx,只让 Claude Code 看到关键信息。既减轻渲染压力,也减少上下文膨胀。
4.4 工具层排查的实操清单
把工具层的问题整理成一个可执行的排查清单:
- 确认命令是否交互式:手动跑一遍,看是否等待输入。
- 确认命令耗时:预估执行时间,长命令单独跑。
- 确认输出量:加
head/grep限制输出。 - 确认工作目录:命令是否在正确的目录执行,避免在大目录里做全量扫描。
- 确认权限:某些命令需要权限,卡在权限提示上也会假死。
这五条基本能覆盖工具层 90% 的卡顿场景。
5. 渲染层与终端环境:Spinner 自己卡住了
5.1 终端性能:不是所有终端都扛得住流式输出
Claude Code 是流式输出,字是一个一个蹦出来的。这对终端渲染是个考验。一些老终端、配置了复杂主题的终端、或者开了大量插件的终端,在流式输出时会出现明显的掉帧,Spinner 动画一顿一顿的。
我对比过几个终端,同样的 Claude Code 会话,在轻量终端里 Spinner 丝滑,在重配置终端里就明显卡。如果你怀疑是渲染层问题,可以换个干净的终端试试,或者临时关掉终端的花哨主题和插件。
5.2 系统资源:CPU 和内存被吃满
渲染卡顿的另一个原因是系统资源紧张。Claude Code 本身、它调用的工具、你的编辑器、浏览器,全都在抢 CPU 和内存。如果内存吃紧开始 swap,整个系统都会卡,Spinner 自然也跟着卡。
排查很简单:卡的时候开个top或任务管理器,看 CPU 和内存占用。如果某个进程吃满了资源,先解决它。这类问题在配置较低的机器上尤其常见。
5.3 终端复用与多会话:别同时开太多
有些人习惯开一堆终端窗口,每个里面跑一个 Claude Code 会话。每个会话都在流式输出、都在调工具,资源竞争会很激烈。我的建议是同一时间专注一个会话,需要并行的时候再开,用完就关。这能显著降低渲染层和系统层的压力。
6. 一套可复现的卡顿排查链路
6.1 第一步:区分"真卡"和"慢"
拿到卡顿现象,先别动手改。观察 30 秒到 1 分钟:
- 如果 Spinner 在转,且偶尔有输出,那是"慢",不是"卡"。
- 如果 Spinner 完全不转,或者转但长时间零输出,那可能是"卡"。
这一步决定了你后面往哪个方向查。
6.2 第二步:按层定位
用前面讲的四层模型逐层排除:
- 看是不是刚发消息就卡→ 模型层/网络层。
- 看是不是出字中途卡→ 工具层。
- 看是不是 Spinner 动画本身卡→ 渲染层/系统资源。
- 看是不是特定操作才卡→ 大概率是那个操作触发的工具层问题。
6.3 第三步:针对性验证
定位到某一层后,做针对性验证:
- 模型层:换个简单请求试试,看是否同样慢。
- 工具层:手动跑那个命令,看是否卡住。
- 渲染层:换终端、看资源占用。
- 网络层:测延迟和丢包。
6.4 第四步:修复与回归
找到根因后修复,然后重复触发那个场景,确认不再卡。这一步很重要,很多人修完就不管了,结果换个场景又卡。回归验证能帮你确认修复是否彻底。
7. 几个我踩过的坑和对应的经验
7.1 别把"上下文太长"当成"软件坏了"
我最开始用的时候,一个会话聊了几十轮,越到后面越慢,我以为软件有问题。后来才明白是上下文膨胀。现在我的习惯是:一个任务一个会话,任务完成就清。这样既快又省。
7.2 交互式命令是隐形杀手
前面提过,这里再强调一次。让 Claude Code 执行命令前,先想一下这个命令会不会等输入。会等的,要么加非交互参数,要么自己手动跑。这个坑我踩过不止一次。
7.3 终端配置越简单越稳
花哨的终端主题、一堆插件,平时看着爽,流式输出时就露馅了。我现在用的终端配置非常朴素,Spinner 从来没卡过。性能这东西,简单就是快。
7.4 长命令自己跑,别全丢给 Claude Code
构建、全量测试、装依赖,这些我自己在终端跑,跑完把结果贴给 Claude Code 分析。这样既避免了工具层卡顿,也让 Claude Code 专注于它擅长的推理和分析。
7.5 卡住时先别急着杀进程
很多人一卡就 Ctrl+C 或者杀进程。但如果它只是在等一个慢响应,杀了反而丢上下文。我的做法是:先观察,确认是真卡死再动手。判断标准就是前面说的——Spinner 转不转、有没有输出。
8. 关于 Spinner 和卡顿,最后再聊几句实在的
Claude Code 的 Spinner 卡顿,本质上是一个"状态可见性"问题。它用一个统一的转圈动画,掩盖了背后四层完全不同的运行状态。你要做的,不是去改 Spinner 本身,而是学会透过它去判断真实状态。
我现在的习惯是:看到 Spinner 转,先在心里过一遍"它现在可能在干嘛"——是在等模型、在跑命令、还是在渲染。这个判断一旦形成,排查就变成了条件反射,几秒钟就能定位方向。
另外,别追求"永远不卡"。任何依赖外部服务、依赖本地命令执行的工具,都会有等待。关键是你能区分"正常等待"和"异常卡死",前者耐心等,后者按链路排查。这个能力,比记住任何一条具体命令都值钱。
如果你也遇到过特别诡异的卡顿场景,欢迎按这套四层模型去拆解,大概率能找到根因。工具是死的,排查思路是活的,多拆几次,你就成了那个"一看 Spinner 就知道问题在哪"的人。