1. 从一个让人抓狂的报错说起如果你在 VS Code 里用 Jupyter 跑过代码大概率见过这个弹窗Kernel died with exit code 1。它不像语法错误那样给你一行红色波浪线也不像缺包那样告诉你ModuleNotFoundError它就是一个冷冰冰的退出码然后内核直接没了。你点重启它又死你换一个 notebook它还是死你甚至开始怀疑是不是电脑该换了。我前后在三个不同的项目里踩过这个坑每次的根因都不一样。最坑的一次是同一个 notebook在浏览器里打开 Jupyter Lab 跑得好好的一放到 VS Code 里就必死。这就说明问题不在代码本身而在环境链路上。VS Code 的 Jupyter 扩展并不是自己实现了一个内核它本质上是去调用你系统里已经装好的 Python 解释器和ipykernel中间隔了好几层VS Code 扩展 → Python 扩展 → 选中的解释器 → 该解释器下的 ipykernel → 实际启动的 kernel 进程。任何一层对不上都可能以 exit code 1 收场。这篇文章就是把这个链路拆开一层一层告诉你哪里容易断以及断了之后怎么快速定位。适合两类人一类是刚配好多环境、正准备大干一场结果被这个报错劝退的另一类是已经能跑但想搞清楚为什么换个环境就崩的。我会把每个环节的排查命令、判断依据、以及我实际踩过的坑都写清楚你照着做基本能覆盖九成以上的场景。2. 先搞清楚 exit code 1 到底是谁报的2.1 内核进程的启动链路很多人一看到 Kernel died 就以为是 Jupyter 的问题其实这个报错是 VS Code 的 Jupyter 扩展在子进程退出时抛出来的。它启动 kernel 的方式大致是这样扩展先通过 Python 扩展拿到你当前选中的解释器路径然后执行类似python -m ipykernel_launcher -f {connection_file}的命令把内核作为一个独立进程拉起来。这个进程如果启动过程中抛了未捕获异常、或者直接sys.exit(1)扩展就会收到退出码 1然后弹出那句话。所以 exit code 1 的含义是内核进程自己退出了而且是非正常退出。它可能是 Python 解释器根本没起来可能是ipykernel没装可能是ipykernel_launcher导入时报错也可能是内核启动后连不上 ZMQ 端口。范围很宽但好消息是——真正的错误信息通常被吞掉了我们需要主动把它捞出来。2.2 为什么错误信息经常看不到VS Code 默认只显示一句 Kernel died因为内核进程的 stderr 被扩展接管了普通输出面板里看不到。这时候有两个地方能救命Output 面板CtrlShiftU打开 Output右上角下拉选 Jupyter这里会打印扩展启动内核的完整命令和部分 stderr。手动复现把 Output 里那条启动命令复制出来在终端里手动跑一遍错误就会原原本本打在你脸上。我强烈建议养成手动复现的习惯。因为 VS Code 的日志有时候会截断而终端不会骗你。下面这条命令就是典型的内核启动命令形态/path/to/python -m ipykernel_launcher -f /tmp/kernel-xxxx.json如果你手动跑这条命令看到的是ModuleNotFoundError: No module named ipykernel那问题就非常明确了——当前解释器没装 ipykernel。如果看到的是别的继续往下看。2.3 一个容易被忽略的前提解释器选中状态在动手排查之前先确认一件事VS Code 当前选中的解释器和你以为的那个是不是同一个。这个听起来很蠢但我见过太多次了。VS Code 右下角、命令面板Python: Select Interpreter、以及 notebook 右上角的 kernel 选择器这三处显示的可能是三个不同的东西。尤其是当你用 conda 或 venv 建了多个环境时VS Code 会记住上次选中的解释器而 notebook 又会记住上次用的 kernel。两者一旦错位就会出现我在 A 环境装了包结果它用 B 环境跑的诡异现象。排查第一步永远是打开一个终端确认which python和python -c import sys; print(sys.executable)的输出和 VS Code 里选中的解释器路径完全一致。不一致就先统一再谈别的。3. 环境错位最常见的头号嫌疑3.1 多环境下的解释器与 kernel 错配这是我自己踩得最多的一类。场景很典型我用 conda 建了个环境叫proj-a在里面装了 pandas、numpy、ipykernel然后打开 VS Code右下角选了解释器proj-anotebook 右上角也选了proj-a结果一跑就死。手动复现发现它实际调用的是 base 环境的 python。原因在于VS Code 的 kernel 选择器里显示的 kernel 列表来自jupyter kernelspec list而不是来自你当前选中的解释器。也就是说即使你选了解释器proj-a如果proj-a没有注册成 kernelspecVS Code 可能会 fallback 到某个已注册的 kernel比如 base 的或者干脆用一个默认路径去启动导致环境错位。判断方法很简单在终端里跑jupyter kernelspec list看看列出来的 kernel 路径是不是你期望的那个环境。如果proj-a根本不在列表里那 VS Code 里选的那个proj-a kernel很可能是假的或者指向了别处。3.2 手动注册 kernelspec 的正确姿势解决办法是给目标环境显式注册一个 kernelspec。注意不要用--user和--sys-prefix混着来这是另一个坑。推荐的做法是先激活目标环境然后在该环境内注册。conda activate proj-a python -m ipykernel install --user --name proj-a --display-name Python (proj-a)这里--user会把 kernelspec 写到用户目录Linux/macOS 是~/.local/share/jupyter/kernels/Windows 是%APPDATA%\jupyter\kernels\好处是不污染系统环境多个环境互不干扰。--name是内部标识--display-name是 VS Code 里显示的名字建议把环境名写进去方便区分。注册完之后重启 VS Code不是重载窗口是完全退出再打开因为 kernel 列表是在启动时缓存的。重启后在 notebook 右上角重新选一次这次应该能看到你刚注册的那个。注意如果你用的是 venv 而不是 conda注册命令一样但激活方式换成source venv/bin/activateLinux/macOS或venv\Scripts\activateWindows。venv 环境默认不会自动注册 kernelspec必须手动来一次。3.3 一个隐蔽的坑kernelspec 里的 argv 写死了路径注册完 kernelspec 后它会在对应目录下生成一个kernel.json。这个文件里有个argv字段写的是启动内核的完整命令。如果你后来移动了环境目录或者重命名了 conda 环境这个argv里的路径就失效了内核启动时找不到 python直接 exit code 1。我遇到过一次把 conda 环境从D:\envs\proj-a挪到了E:\envs\proj-a结果所有 notebook 全崩。排查了半天才想起来是kernel.json里的路径没更新。解决办法就是删掉旧的 kernelspec 重新注册jupyter kernelspec remove proj-a # 然后重新 install或者直接手动编辑kernel.json里的路径。但手动编辑容易漏还是重新注册稳妥。4. ipykernel 缺失或版本不匹配4.1 新环境里最容易漏装的东西新建一个 conda 环境后很多人第一件事是pip install pandas numpy然后打开 VS Code 写 notebook结果内核直接死。原因就是新环境里没有 ipykernel。ipykernel不是 Python 自带的也不是 conda 建环境时默认装的除非你用了anaconda这个 meta 包必须手动装。conda activate proj-a pip install ipykernel # 或者 conda install ipykernel装完之后再注册 kernelspec顺序不能反。我见过有人先注册再装 ipykernel结果注册时用的是旧状态kernel.json 里指向的模块不存在照样死。4.2 版本冲突ipykernel 和 jupyter_client 打架比没装更隐蔽的是装了但版本不兼容。ipykernel依赖jupyter_client而jupyter_client又依赖pyzmq。这三个包的版本如果对不上内核启动时会在建立 ZMQ 连接那一步挂掉报错信息往往是一堆 traceback最后以 exit code 1 结束。典型症状是手动跑python -m ipykernel_launcher -f xxx.json时看到类似ImportError: cannot import name xxx from jupyter_client或者zmq.error.ZMQError。这时候不要一个个去猜版本直接用 pip 的依赖解析来修pip install --upgrade ipykernel jupyter_client pyzmq如果升级后还是不行可能是某个包被 conda 和 pip 混装搞乱了。这种情况我建议在一个干净环境里重装而不是在污染环境里修。因为 conda 和 pip 混用导致的依赖树错乱修起来的时间成本远高于重建。4.3 用 conda 还是 pip 装 ipykernel这个问题没有绝对答案但有个原则如果你这个环境里的其他包主要是 conda 装的那 ipykernel 也用 conda 装如果主要是 pip 装的那就用 pip。混着来最容易出问题因为 conda 和 pip 各自维护一套依赖记录互相不知道对方干了什么。我个人的习惯是科学计算类环境用 conda 装核心包numpy、scipy、pandasipykernel 也跟着 conda纯 Web 或工具类环境用 pipipykernel 也 pip。这样依赖树相对干净出问题的概率低很多。5. 端口、权限与路径里的暗雷5.1 ZMQ 端口被占用或无法绑定内核和前端之间通过 ZMQ 通信需要绑定几个本地端口。如果这些端口被占用或者系统限制导致无法绑定内核启动就会失败。这种情况在 Windows 上尤其常见因为 Windows 的端口回收比较慢频繁重启内核时容易撞上。判断方法手动启动内核时如果看到OSError: [Errno 98] Address already in use或者zmq.error.ZMQError: Address already in use那就是端口问题。解决办法有几个等一会儿再试让系统回收端口。重启 VS Code释放它持有的连接。如果是 Windows检查是否有残留的 python 进程占着端口用任务管理器杀掉。# Linux/macOS 查看占用端口的进程 lsof -i :8888 # Windows netstat -ano | findstr :88885.2 工作目录不存在导致的启动失败这个坑非常隐蔽。内核启动时会尝试cd到 notebook 所在的工作目录。如果这个目录不存在比如你删了文件夹但 VS Code 还开着那个 notebook或者没有访问权限内核进程会在初始化阶段直接退出报 exit code 1。我遇到过一次把项目文件夹从~/work/proj重命名成了~/work/proj-v2但 VS Code 的工作区还指向旧路径结果所有 notebook 全崩。排查时手动跑内核命令发现它cd到一个不存在的目录然后就没有然后了。解决办法关掉 VS Code重新打开正确的文件夹。如果 notebook 文件本身还在用打开文件夹的方式重新加载而不是直接双击 notebook 文件。5.3 路径里有中文或空格Windows 用户特别容易踩这个。如果你的 Python 环境路径、或者 notebook 所在路径里包含中文、空格、特殊字符某些版本的 ipykernel 在拼接命令行参数时会出错导致内核启动失败。这个问题的表现是手动跑命令时看起来正常但 VS Code 启动时就死。规避方法很简单Python 环境路径和项目路径都用纯英文、无空格。比如D:\envs\proj-a和D:\work\proj-a不要用D:\我的环境\项目 A。虽然现代工具对中文路径的支持越来越好但在内核启动这种涉及多层进程调用的场景里少一个变量就少一个坑。6. 一套可复用的排查流程6.1 从 Output 面板拿到真实命令前面提过第一步永远是打开 Output 面板选 Jupyter找到类似这样的日志Starting kernel: /path/to/python -m ipykernel_launcher -f /tmp/kernel-xxx.json把这条命令完整复制出来。注意有时候日志里会有多行要找到最后实际执行的那条。如果日志里没有可以在 VS Code 设置里把jupyter.logKernelOutput打开它会打印更多细节。6.2 在终端里手动复现拿到命令后在终端里跑一遍。这一步的关键是用和 VS Code 相同的 shell 环境。如果你在 VS Code 里用的是 PowerShell那就在 PowerShell 里跑如果用的是 bash就用 bash。因为不同 shell 的环境变量加载逻辑不一样可能影响 Python 的模块搜索路径。手动跑的时候观察输出如果报No module named ipykernel去装 ipykernel。如果报ImportError或AttributeError是版本冲突去升级相关包。如果报Address already in use是端口问题去释放端口。如果报FileNotFoundError或PermissionError是路径或权限问题。如果什么都没报进程直接退出那可能是kernel.json配置有问题去检查 kernelspec。6.3 逐层验证的检查清单为了让你少走弯路我把整个链路的检查点整理成一张表按顺序排查即可检查层检查内容命令/方法常见问题解释器VS Code 选中的解释器路径Python: Select Interpreter选错环境解释器终端里 python 的实际路径which python/where python与 VS Code 不一致包ipykernel 是否安装pip show ipykernel新环境漏装包依赖版本是否兼容pip checkjupyter_client 冲突kernelspec是否注册jupyter kernelspec list未注册或路径失效kernelspeckernel.json 里的 argv查看对应目录下文件路径写死且已失效端口ZMQ 端口是否可用lsof -i/netstat -ano端口被占用路径工作目录是否存在ls/dir目录被删或改名路径是否含中文/空格目测特殊字符导致解析失败按这个表从上往下走基本能在十分钟内定位到问题层。我自己的经验是前四行能解决八成问题后面几行属于偶发但一旦遇到就很折磨的类型。6.4 重建环境最后的杀手锏如果排查了一圈还是没头绪或者环境已经被 conda/pip 混装搞得千疮百孔那就别修了直接重建。重建的步骤conda create -n proj-new python3.11 conda activate proj-new pip install ipykernel pandas numpy matplotlib python -m ipykernel install --user --name proj-new --display-name Python (proj-new)然后重启 VS Code选新环境。整个过程十分钟以内比在一个坏环境里折腾两小时划算得多。我现在的原则是环境一旦出现无法解释的内核崩溃且排查超过二十分钟直接重建。时间比什么都贵。7. 几个我实际踩过的真实场景7.1 场景一conda 环境重命名后全崩有一次为了统一命名规范我把几个 conda 环境从env1、env2改成了proj-data、proj-model。改完之后所有 notebook 全部报 kernel died。手动跑内核命令发现kernel.json里的argv还指向旧路径env1/bin/python而这个路径已经不存在了。解决方式就是删掉旧 kernelspec 重新注册。但这里有个细节jupyter kernelspec remove用的是--name参数也就是注册时的内部标识不是显示名。如果你忘了内部标识是什么可以去 kernelspec 目录下看文件夹名那个就是。7.2 场景二Windows 下路径含空格导致启动失败某次在 Windows 上Python 装在C:\Program Files\Python311路径里有空格。VS Code 启动内核时命令行参数拼接没有正确加引号导致ipykernel_launcher收到的-f参数被截断内核启动失败。手动在终端里跑同样的命令却正常因为终端会自动处理引号。这个问题的根因在扩展的参数拼接逻辑上用户层面能做的就是把 Python 装到无空格路径比如C:\Python311。虽然听起来很原始但确实能绕开这类问题。7.3 场景三jupyter_client 版本过新导致不兼容有一次手贱pip install --upgrade jupyter_client升到了最新版结果 ipykernel 还是旧版两者 API 对不上内核启动时报AttributeError。这种升级了一个包但没升级配套包的操作是版本冲突的经典来源。教训就是升级 Jupyter 相关包时要么一起升要么都别动。如果只想升一个先看看它的依赖关系确认配套包是否兼容。pip check可以帮你发现这类问题但它只在安装后检查启动时的问题还得靠手动复现来暴露。8. 把坑填上之后的一些习惯踩了这么多次之后我养成了几个习惯分享出来供参考。第一新建环境后第一件事就是装 ipykernel 并注册 kernelspec不要等到写 notebook 时才想起来。第二环境路径和项目路径一律用英文无空格从源头上规避路径解析问题。第三不轻易混用 conda 和 pip 装同一个环境的包如果非要混先装 conda 的再用 pip 补缺。第四遇到内核崩溃先看 Output 面板再手动复现不要盲目重启重启解决不了根因。还有一个很实用的小技巧在 notebook 的第一个 cell 里跑import sys; print(sys.executable)确认当前内核用的到底是哪个 Python。这个习惯能帮你在一开始就发现环境错位而不是等到跑了一半才崩。我现在的每个 notebook 第一个 cell 都是这行已经成了肌肉记忆。内核崩溃这件事说到底就是链路太长、信息被吞、环境太乱三个因素叠加。把链路拆开、把信息捞出来、把环境理干净exit code 1 就不再是玄学而是一个有明确原因的普通报错。希望这篇记录能帮你少走几个我走过的弯路。