ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Claude Code卡顿排查指南:从Spinner状态机到全链路性能诊断

Claude Code卡顿排查指南:从Spinner状态机到全链路性能诊断

1. 从Spinner说起:这个转圈到底在转什么

用Claude Code的人大概都遇到过这种场景:敲完一段提示词,终端里那个小小的Spinner开始转,你盯着它转了三秒、五秒、十秒,然后它还在转。你开始怀疑是不是网络断了,是不是模型挂了,是不是自己命令写错了。等你忍不住Ctrl+C重来一遍,结果发现刚才那次其实已经在正常执行,只是Spinner没给你任何有意义的反馈。

这个Spinner,说白了就是CLI界面里的一个状态指示器。它本身不复杂,就是在终端里循环渲染几个字符帧,让用户知道“程序还活着,正在干活”。但问题在于,Claude Code的Spinner承载的信息量太少了。它不像传统编译工具的进度条那样有百分比,也不像包管理器的下载进度那样有速率显示。它就是一个纯粹的“我在忙”信号,至于忙什么、忙到哪一步了、还要多久,全靠用户自己猜。

我刚开始用Claude Code的时候,一度以为Spinner卡住就是程序死了。后来翻了一些源码和社区讨论才明白,Spinner的渲染和实际的任务执行是两条独立的异步链路。Spinner在转,不代表任务在推进;Spinner停了,也不代表任务结束了。这个认知差是很多误判的根源。

提示:Spinner的本质是一个UI层的动画循环,它和底层模型调用、工具执行、文件读写这些实际工作没有强绑定关系。理解这一点,后面排查卡顿的时候才不会把方向搞偏。

那为什么Claude Code要用这么“简陋”的状态提示?我的理解是,CLI工具的交互范式决定了它很难做复杂的进度展示。终端不像GUI那样有丰富的控件,你没法在命令行里画一个漂亮的进度条还带ETA估算。而且Claude Code的工作流本身是非确定性的——模型可能调一次工具就完事,也可能连续调十几次工具、读几十个文件、跑好几轮推理。这种不确定性让精确的进度估算变得几乎不可能。

所以Spinner的设计选择是合理的,但它带来的副作用就是:用户对“卡住”和“正在工作”的边界感知非常模糊。尤其是在网络延迟高、模型负载大的时候,Spinner转得欢但实际响应迟迟不来,体验就很差。

1.1 Spinner背后的状态机长什么样

要搞清楚卡顿问题,得先知道Claude Code在运行时的状态流转大概是什么样。根据我自己的使用观察和社区里一些逆向分析的资料,Claude Code的核心状态大致可以分成这么几层:

  • 空闲态:等待用户输入,Spinner不显示或者显示一个静态提示符。
  • 请求态:用户提交输入后,CLI把请求打包发给模型服务端,这时候Spinner开始转。
  • 流式接收态:服务端开始返回token流,CLI边接收边渲染,Spinner可能还在转,也可能切换成其他指示。
  • 工具执行态:模型决定调用某个工具(比如读文件、执行命令),CLI在本地执行这些工具调用,Spinner继续转。
  • 多轮循环态:工具执行结果返回给模型,模型继续推理,可能再次调用工具,形成循环。
  • 完成态:模型输出最终结果,Spinner停止,控制权交还用户。

问题出在“请求态”和“工具执行态”这两个阶段。请求态受网络和服务端负载影响,可能长时间没有数据返回;工具执行态受本地环境影,比如执行一个耗时的shell命令,或者读取一个超大文件,都会让Spinner转很久但没有任何中间反馈。

更麻烦的是,这两个阶段在用户看来是一模一样的——都是Spinner在转,没有任何区分。你根本不知道当前是在等网络,还是在等本地命令跑完。这种信息黑箱是导致“频繁卡住”体感的核心原因。

1.2 为什么你总觉得它卡住了

这里面有个心理学层面的因素:不确定性带来的焦虑放大。如果Spinner旁边能显示“正在等待模型响应”或者“正在执行npm install”,你的容忍度会高很多。但当你面对一个没有任何文字说明的旋转字符时,每一秒的等待都会被主观放大。

我做过一个粗糙的测试:同样的一个任务,一次让Claude Code直接跑,一次我在旁边用另一个终端跑一个无关的耗时命令来分散注意力。结果前者我觉得等了“好久”,后者我觉得“还行”。实际耗时几乎一样。这说明卡顿感很大程度上是注意力聚焦在Spinner上导致的。

当然,这不意味着所有卡顿都是错觉。确实有一些真实的性能问题会让Claude Code的响应变得异常缓慢。下面几节我会把常见的卡顿根源逐个拆开讲。

2. 卡顿根源拆解:从网络到本地的全链路排查

Claude Code的卡顿不是一个单一问题,它是多个环节可能出状况的叠加。我把它分成四大类:网络层、服务端层、本地环境层、配置层。每一类的表现和排查方法都不一样。

2.1 网络层:最容易被误判的环节

网络问题是最常见的卡顿来源,但也是最难确认的。因为Claude Code的Spinner不会告诉你“我正在等网络响应”。你只能通过一些间接手段来判断。

我常用的方法是:在另一个终端窗口里持续ping一个稳定的公共DNS,比如ping 8.8.8.8或者ping 1.1.1.1。当你觉得Claude Code卡住的时候,瞄一眼ping的延迟和丢包情况。如果ping值突然飙高或者开始丢包,那基本可以确定是网络层面的问题。

另一个方法是看Claude Code的日志输出。如果你开了verbose模式(通常是通过--verbose参数或者配置文件里的日志级别设置),CLI会打印一些请求相关的信息。虽然这些信息不一定直接显示网络延迟,但你能看到请求是什么时候发出的、什么时候收到第一个响应字节。

网络层卡顿的典型表现是:Spinner转了很久,然后突然一次性输出一大段内容。这说明请求发出去了,但服务端的响应在网络上堵了很久,或者服务端本身处理了很久才返回。

注意:如果你在公司内网或者有代理的环境下使用,网络路径可能更复杂。代理的稳定性、DNS解析速度、TLS握手耗时都会影响整体响应。这种情况下建议先用curl测一下到服务端的基础连通性和延迟。

2.2 服务端层:你控制不了但能感知的部分

服务端层的卡顿通常表现为:网络没问题,本地也没问题,但就是响应慢。这种情况在模型服务负载高的时候比较常见,比如某些时段用户集中使用,服务端的推理队列排得很长。

判断服务端层问题的方法是排除法。你先确认网络正常(ping稳定、curl测试通过),再确认本地环境正常(没有CPU跑满、没有磁盘IO瓶颈),如果还是卡,那大概率就是服务端的问题。

这种卡顿你基本无能为力,只能等。但你可以做一些事情来减少影响:比如把大任务拆成小任务分批提交,避免一次请求触发太多轮工具调用;比如在非高峰时段使用;比如配置一个合理的超时时间,避免无限等待。

2.3 本地环境层:最容易被忽视的瓶颈

很多人把卡顿归咎于网络或服务端,但实际上本地环境的问题同样常见。Claude Code在运行过程中会在本地做很多事情:读写文件、执行shell命令、解析项目结构、维护上下文缓存。这些操作如果遇到瓶颈,Spinner照样会转很久。

常见的本地瓶颈包括:

  • 磁盘IO慢:如果你的项目在机械硬盘上,或者在一个网络挂载的文件系统上,Claude Code读取文件的速度会非常慢。尤其是当它需要扫描大量文件来理解项目结构时,磁盘IO会成为明显的瓶颈。
  • CPU占用高:Claude Code本身是一个Node.js应用,虽然不算特别吃CPU,但如果你同时跑着其他重负载任务(比如编译、虚拟机、大型IDE的索引),CPU资源竞争会导致CLI响应变慢。
  • 内存不足:Node.js应用对内存比较敏感,如果系统内存紧张,频繁的GC会导致卡顿。
  • 终端渲染性能:这个比较隐蔽。某些终端模拟器在处理大量输出或者复杂ANSI转义序列时性能很差,导致Spinner的渲染本身就不流畅,看起来像卡住了。

我有一个实际案例:在一个大型monorepo项目里用Claude Code,每次它尝试读取项目结构时都会卡很久。后来发现是因为项目目录下有几十万个文件(包括node_modules和构建产物),Claude Code的文件扫描逻辑在这个规模下性能急剧下降。解决办法是在项目根目录加一个忽略配置,把不需要的目录排除掉。

2.4 配置层:参数没调对,卡顿是必然的

Claude Code有一些配置参数会直接影响性能和响应速度。如果这些参数没调好,卡顿几乎是必然的。

比如超时设置。如果超时时间设得太短,请求还没完成就被中断,你会看到频繁的重试和卡顿。如果设得太长,真正卡住的时候你要等很久才能得到反馈。找到一个合理的平衡点很重要。

比如上下文窗口大小。Claude Code会把项目相关的上下文一起发给模型,上下文越大,服务端处理时间越长,网络传输量也越大。如果你的项目很大,而上下文配置没有做合理的裁剪,每次请求都会携带大量无关信息,导致响应缓慢。

比如并发工具调用的数量。有些配置允许模型同时调用多个工具,这在理论上能提高效率,但如果本地环境处理不过来(比如同时执行多个耗时的shell命令),反而会导致整体卡顿。

3. 实操排查:一套可复用的诊断流程

说了这么多理论,接下来给一套我自己常用的排查流程。这套流程从外到内、从简到繁,基本能覆盖大部分卡顿场景。

3.1 第一步:确认是“真卡”还是“假卡”

前面说过,Spinner在转不代表任务在推进,Spinner看起来停了也不代表任务死了。所以第一步是确认当前到底是哪种状态。

我通常这样做:

  1. 先等30秒。如果30秒内没有任何输出变化,进入下一步。
  2. 按一次回车键(不是Ctrl+C)。有些情况下CLI只是在等待某个输入或者确认,回车能触发下一步。
  3. 如果回车没反应,尝试按Ctrl+C一次。注意是一次,不是连按。如果程序响应了Ctrl+C并退出了,说明它其实还活着,只是没有输出。如果连Ctrl+C都没反应,那可能是真的卡死了。
  4. 在另一个终端里检查系统资源:top或者htop看CPU和内存,iostat看磁盘IO。如果Claude Code进程的CPU占用是0%,那它可能在等网络;如果CPU占用很高,那它在本地计算;如果磁盘IO很高,那它在读写文件。

这个流程能帮你快速定位卡顿发生在哪个层面。

3.2 第二步:开日志,看细节

Claude Code支持不同级别的日志输出。默认情况下日志比较简洁,但你可以通过环境变量或者配置文件打开更详细的日志。

通常我会设置DEBUG或者VERBOSE环境变量来开启调试日志。具体变量名可能因版本而异,建议查一下你所用版本的官方文档。开启后,CLI会打印请求的发送时间、响应时间、工具调用的开始和结束时间等信息。这些时间戳能帮你精确判断时间花在了哪里。

如果日志显示请求发出后很久才收到第一个响应,问题在网络或服务端。如果日志显示工具调用开始后很久才结束,问题在本地工具执行。如果日志显示请求和响应都很快,但Spinner就是不停,那可能是UI渲染层面的问题。

3.3 第三步:逐层排除

根据前两步收集到的信息,开始逐层排除。

网络层排查:

# 测试基础连通性和延迟 ping -c 10 <服务端域名或IP> # 测试HTTPS握手和响应时间 curl -o /dev/null -s -w "DNS解析: %{time_namelookup}s\nTCP连接: %{time_connect}s\nTLS握手: %{time_appconnect}s\n首字节: %{time_starttransfer}s\n总计: %{time_total}s\n" <服务端URL>

如果DNS解析时间超过100ms,考虑换一个更快的DNS。如果TLS握手时间超过500ms,可能是网络路径有问题。如果首字节时间很长但连接建立很快,那可能是服务端处理慢。

本地环境排查:

# 查看CPU和内存占用 top -b -n 1 | head -20 # 查看磁盘IO iostat -x 1 3 # 查看Claude Code进程的资源占用 ps aux | grep claude

如果发现Claude Code进程的CPU或内存占用异常高,可能是遇到了某个性能bug,或者项目规模超出了它的处理能力。

配置层排查:

检查你的配置文件,确认以下参数是否合理:

参数建议值说明
请求超时60-120秒太短会导致频繁中断,太长会导致卡住时等待过久
上下文窗口根据项目大小调整大项目建议开启智能裁剪
工具调用超时30-60秒避免单个工具执行卡住整个流程
日志级别排查时设为debug日常使用设为info或warn
文件扫描忽略配置node_modules等避免扫描无关文件

3.4 第四步:针对性优化

根据排查结果做针对性优化。如果是网络问题,考虑换个网络环境或者调整超时参数。如果是本地环境问题,清理项目目录、关闭无关进程、升级硬件。如果是配置问题,按上面的表格调整参数。

我自己的经验是,大部分卡顿问题通过调整文件扫描忽略配置和超时参数就能明显改善。真正需要改网络或者换硬件的场景其实不多。

4. 常见问题速查与避坑经验

这一节整理一些我在使用过程中遇到的具体问题和解决方法,以及一些社区里常见的坑。

4.1 Spinner转了很久然后报超时

这是最常见的情况。Spinner转了超过你设置超时时间,然后CLI报了一个超时错误。

原因通常是:请求发出去了,但服务端在超时时间内没有返回完整响应。可能是网络慢,可能是服务端负载高,也可能是你的请求触发了太复杂的工具调用链。

解决办法:先适当调大超时时间,比如从30秒调到90秒。如果调大后还是超时,检查网络。如果网络没问题,尝试把任务拆小,减少单次请求的复杂度。

4.2 Spinner卡住不动,Ctrl+C也没反应

这种情况比较少见,但一旦遇到就很烦。通常是因为CLI进程陷入了某个死循环,或者在等待一个永远不会到来的IO事件。

解决办法:在另一个终端里找到Claude Code的进程ID,先尝试kill -TERM,如果不行再kill -KILL。然后检查日志,看最后卡在哪个操作上。如果是某个特定文件或目录导致的,把它加入忽略列表。

4.3 在VS Code里用Claude Code特别卡

VS Code本身是一个Electron应用,资源占用不低。如果你同时在VS Code里跑着其他扩展(比如大型语言服务的扩展、实时lint、文件索引),再加上Claude Code,资源竞争会很严重。

我的建议是:在用Claude Code跑重任务的时候,暂时禁用一些不必要的VS Code扩展。或者干脆在独立的终端里用Claude Code,不通过VS Code的集成终端。

4.4 项目大了之后越来越卡

这是很多人的共同体验。项目小的时候Claude Code很流畅,项目大了之后每次操作都要等很久。

根本原因是Claude Code需要读取和理解项目结构,项目越大,这个过程的耗时越长。解决办法是配置合理的忽略规则,把构建产物、依赖目录、日志文件、缓存目录都排除掉。另外可以考虑把大项目拆成多个小项目,或者只在需要的时候让Claude Code关注特定的子目录。

4.5 升级版本后卡顿加剧

有时候新版本引入了新的功能或者改变了默认配置,可能导致性能变化。如果升级后发现明显变卡,先检查配置文件是否有变化,然后看看新版本有没有引入新的日志输出或者调试信息。

回滚到上一个稳定版本也是一个选择。不过建议先花点时间排查,因为新版本可能修复了其他更重要的问题。

提示:遇到卡顿问题的时候,先别急着重装或者换工具。花五分钟做一下上面说的分层排查,大部分情况下你能定位到具体原因。盲目重装往往解决不了问题,还会浪费大量时间。

4.6 一个容易被忽视的细节:终端本身

我用过一些终端模拟器,在处理Claude Code的输出时表现差异很大。有的终端在大量文本输出时会明显卡顿,有的则很流畅。如果你试了各种方法都找不到原因,不妨换一个终端试试。

另外,终端里的字体、颜色主题、透明效果这些视觉设置也会影响渲染性能。把透明效果关掉、换一个简单的配色方案,有时候能带来意想不到的改善。

5. 一些个人体会和后续可以做的事

我用Claude Code有一段时间了,从最初的“这玩意儿怎么老卡”到现在的“基本能判断出卡在哪”,中间踩了不少坑。最大的体会是:不要把所有卡顿都当成同一个问题。网络卡、服务端卡、本地卡、UI卡,表现可能差不多,但原因和解决方法完全不同。

另一个体会是:日志是你最好的朋友。花点时间把日志配置好,遇到问题的时候能省下大量猜测的时间。很多人不愿意看日志,觉得麻烦,但实际上日志里往往直接写着答案。

后续我打算继续观察几个方向:一是不同终端模拟器对Claude Code渲染性能的影响,二是大项目下文件扫描的优化空间,三是多任务并发时资源调度的策略。这些方向上的改进可能会让Claude Code在复杂场景下的表现更好。

如果你也在用Claude Code并且遇到了卡顿问题,建议先从最简单的排查做起:看看网络、看看系统资源、看看日志。大部分问题不需要复杂的工具就能定位。实在搞不定的,去社区里搜一下,大概率有人遇到过类似的情况。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进