最近我自己的 Mac 干了一件挺离谱的事:晚上合盖睡觉前电量还有 90%,第二天早上打开,电量居然掉到快没了。屏幕全程是黑的,一次都没碰过它。

我第一反应是电池老化了,后来去翻系统的电池记录才发现不对劲------"屏幕打开时用量"那一栏整晚都是 0 分钟,说明这段时间人确实没碰电脑,可"电池电量"曲线却在同一段时间一路下滑到接近 0%。
这两条曲线对不上,说明问题不在电池,也不在我,而在系统里有个东西没老实待着。
顺手排查了一圈,发现根子是 Cursor(我常用的一个 AI 编程工具,基于 Electron) 没有把进程收拾干净,一直在后台举手不让系统睡觉。顺手整理一下这次排查的过程,希望对你也有用。
太长不读:
- 屏幕黑了不等于电脑睡了,这是两件独立的事,别再被这个直觉骗了。
- 系统睡前会问所有程序"能不能睡",只要有一个程序不放手,整机就只关屏、不省电。
- 这次真凶是 Cursor,一个系统命令就能当场抓现行,文章里有截图和命令,照着抄就行。
一、先说为什么:这事的本质不是"电池不行了"
遇到问题我习惯先问一句:这个问题的本质是什么,而不是急着换电池或者重装系统。掉电快这个现象,背后其实是两件事被我们习惯性地混为一谈了:
- 显示器睡眠:合盖或者锁屏之后,屏幕背光关掉,这只是省了屏幕的电,CPU、GPU、网络芯片该干嘛还干嘛。
- 系统睡眠:整机真正进入低功耗状态,所有硬件几乎都停下来,这时候才叫真的省电,一夜掉 2%~5% 都算正常。
很多人(包括之前的我)默认"屏幕黑了 = 电脑睡了",其实这中间还差着一步:系统在关屏之后,会挨个问一圈当前运行的程序,"我要睡了,你们谁反对?"
这一步在专业说法里叫"睡眠断言(Sleep Assertion)",随便一个程序举个手,说"等等我还有事没干完",系统就会妥协,只把屏幕关掉,自己继续硬扛着不睡。
这个机制本身没毛病,是给正经场景用的,比如你正用 AirDrop 传文件、正在下载东西,这时候确实不该让机器睡死过去。但要是某个程序活干完了,或者你已经彻底退出它了,它却忘了把手放下去,那系统就会被这只看不见的手架着,一整夜维持清醒。
二、动手抓现行:谁在举手不让电脑睡
光靠猜没用,得找一手证据。macOS 自带的 pmset 命令能直接问系统"现在谁在拦着不让睡":
perl
$ pmset -g | grep sleep
sleep 1 (sleep prevented by sharingd, powerd, Cursor)
系统给的原话很直接:睡眠被 sharingd、powerd、Cursor 这几个进程挡住了。再往下深挖一层,能看到具体是哪种断言,举了多久:
css
$ pmset -g assertions | grep -i cursor
pid 16900(Cursor): NoIdleSleepAssertion named: "Electron"
NoIdleSleepAssertion 就是前面说的那只"举手",持有者写得清清楚楚:Cursor。
我又多观察了几分钟,发现更有意思的一点------这只手不是一直举着,而是反复举起放下,间隔几十秒重查一次,一会儿有一会儿没有。这说明大概率是它在后台干活(索引、扩展、AI 对话、文件监听之类)的时候申请了断言,但活干完之后没有老老实实把手放下去,干完一轮又申请一次。
把这条因果链画出来,一眼就能看懂:

三、别慌,先分清哪些是正常的
看到 pmset 输出一堆进程名,很容易一惊一乍,其实不用。
powerd 的断言只在屏幕亮着的时候生效,合盖就自动消失,别管它;sharingd(隔空投送相关)、bluetoothd 一般也是短时的正常行为,来去匆匆。真正值得警惕的,是你已经彻底退出的程序,却还反复出现在这份名单里------这才是需要动手处理的信号。
四、怎么处理
我一贯的习惯是先做最小可行的验证,能一行命令解决的事,不折腾复杂方案。
第一步,随时自查
perl
pmset -g | grep sleep
输出里如果出现你正在用的开发工具名字,说明它此刻正拦着不让睡;没有就是安全的。
第二步,手动清理
perl
# 完整退出该应用(不是关个窗口了事),退出后再确认一遍
pmset -g | grep sleep
# 万一退出后进程还赖着不走,直接找出来杀掉
ps aux | grep -i cursor | grep -v grep
kill -9 <上面查到的进程号>
第三步,写个脚本把这事自动化
问题不大,但既然是间歇性发作的,手动抽查很难抓全,索性把它变成一个能自己跑的小工作流:写一个脚本,每 5 分钟记一次"谁在拦着不让睡",配个开机自启,第二天直接翻日志就知道昨晚几点开始出问题的。这就是我一直强调的那句话------把一次性排查沉淀成流程,而不是每次都靠人肉盯着。
| 监控脚本 | ~/bin/sleep_assertion_monitor.sh |
|---|---|
| 开机自启配置 | ~/Library/LaunchAgents/com.local.sleepassertionmonitor.plist |
| 日志文件 | ~/Library/Logs/SleepAssertionMonitor/assertions.log |
部署完之后不用管它,开机自动加载。第二天想看结果就一行命令:
javascript
grep 警告 -A1 ~/Library/Logs/SleepAssertionMonitor/assertions.log
五、这事不是我一个人踩了坑
好在这不是我一个人的孤例,去 Cursor 官方论坛翻了一圈,发现一堆人早就在报同一类问题,只是表现形式各不一样:
| 具体表现 | 帖子 |
|---|---|
| 关窗口再复用之后,扩展进程被扔在那不管,陷入接近满负荷的死循环,普通退出信号根本喊不动它 | Extension-host processes orphaned after window close/reuse |
| 重新加载窗口或者应用更新之后,后台服务子进程压根没清干净,越攒越多,最后把内存和端口都占满 | MCP Process Leak / Orphaned Children on Restart |
| 终端里跑的命令崩了之后留下一堆僵尸进程,只能靠重启整个应用才能清掉 | Terminal Segmentation Faults Create Zombie Processes |
| 一堆终端相关的孤儿进程越堆越多,把系统资源耗尽,新终端直接打不开 | Cursor PTY Exhaustion Causing Terminal Failures |
| 相关进程在 macOS 上死活不退出,内存被吃光,机器直接卡死 | Cursor Helper processes never terminate on macOS |
其中一个帖子里官方的人还追问用户"完整退出是不是就能清干净了",当时那位用户说可以,但后面陆续有别的用户回复说,就算完整退出,进程照样赖着不走。
也就是说这事触发条件不太统一,官方也还没正式说修好了 。既然是开源社区常见的套路,我建议大家把自己的 pmset 截图、版本号、装了哪些扩展这些一手信息,回复到已有的帖子底下,比另开一贴更容易被捞到优先级。
六、写在最后
说实话,这种"看起来很玄学"的 bug 最烦人的地方,不是它难修,而是大部分人遇到了只会归因成"我电脑不行了",然后要么忍着,要么换新电脑,从头到尾都没弄明白发生了什么。
其实这里最重要的不是背下 pmset 这几个命令,而是养成一个习惯:遇到奇怪现象,先别急着下结论,回到第一性原理去问一句"这背后到底是什么在起作用" 。系统睡没睡、进程退没退干净,这些东西平时看不见,但只要肯往下挖一层,一条命令就能把黑箱变成白箱。
这个观点只代表我自己,也不一定对,但希望能帮你少踩一点坑。