本文除此说明外全部内容均为 AI 生成,鉴于这个问题对我而言频繁出现且 AI 基于这个做法能成功修复,故特此收录。
VSCode Codex 扩展打不开排查记录
NFS 主目录场景下 Codex 扩展面板空白的排查与修复过程,包括走过的弯路。
现象
VSCode 打开 Codex 侧边栏只有一个 logo,没有任何内容。Reload Window 无效。
环境
- 机器:NCSA Delta(aarch64 Linux)
$HOME=/u/tchen19,挂载方式 NFS(harbor-dt.delta.internal.ncsa.edu:/harbor/ncsa/deltaai/u)/tmp、/var/tmp= 本地 XFS(/dev/nvme1n1)/tmp有 per-user bind mount(/tmp/usertmp/<user>)- Codex 扩展:
openai.chatgpt
关键诊断命令
先把"打不开"这个现象转化成可观察的事实。
1. 看扩展自己的日志(最快的线索)
LATEST=$(ls -dt ~/.vscode-server/data/logs/*/ | head -1)
cat "$LATEST/exthost1/openai.chatgpt/Codex.log" | tail -60
看到的错误:
[CodexMcpConnection] cli: message="WARNING: proceeding, even though we could not update PATH: File exists (os error 17)"
[CodexMcpConnection] cli: message="Error: error loading default config after config error: File exists (os error 17)"
[CodexMcpConnection] Codex process fatal error ... Codex app-server process exited unexpectedly (code=1)
2. 找扩展调用的 codex 二进制
路径:~/.vscode-server/extensions/openai.chatgpt-<version>-linux-arm64/bin/linux-aarch64/codex
3. 直接运行二进制,复现问题
--version 本来不需要读配置,竟然也卡住 → 启动路径里一定有某种外部依赖(锁、网络)。
4. 看进程状态(D state = 内核阻塞)
ps -ef | grep codex | grep $USER | grep -v grep
cat /proc/<pid>/status | grep State # State: D (disk sleep)
cat /proc/<pid>/wchan # rpc_wait_bit_killable
ls -la /proc/<pid>/fd # 看它打开了什么 fd
关键发现:
State: D+wchan = rpc_wait_bit_killable→ 在内核里等 NFS RPC 返回fd里有一个~/.codex/tmp/arg0/codex-arg0XXXX/.lock→ 正在flock()这个文件
5. 验证是不是 NFS 锁的问题
timeout 3 flock -n ~/.codex/auth.json -c 'echo ok' # 卡住 → exit 124
timeout 3 flock -n /tmp/test -c 'echo ok' # 立刻 ok
看起来 NFS 的 lockd 坏了。但这个结论后面证明是错的。
弯路 1:把 ~/.codex 软链接到 /tmp
既然 NFS 锁坏了,那就把 Codex 的状态挪到本地磁盘:
mkdir -p /tmp/codex-<user>
rsync -a --exclude 'tmp/arg0/**' ~/.codex/ /tmp/codex-<user>/
mv ~/.codex ~/.codex.nfs-backup
ln -s /tmp/codex-<user> ~/.codex
Reload VSCode —— 还是打不开,而且 /tmp/codex-<user> 整个目录被删掉了。
为什么 /tmp 会被清掉
这台 Delta 节点的 /tmp 实际是 /tmp/usertmp/<user> 的 per-user bind mount(cat /proc/self/mountinfo | grep /tmp 能看到),并且有某种清理机制(/var/tmp/clean_processes.log、/var/tmp/idle-killer.log 都是 root 写的)。
换到 /var/tmp(本地 XFS 但全局共享,不做 per-user 清理):
mkdir -p /var/tmp/codex-<user>
rsync -a ~/.codex.nfs-backup/ /var/tmp/codex-<user>/
rm ~/.codex && ln -s /var/tmp/codex-<user> ~/.codex
Reload —— 还是打不开,而且 /var/tmp/codex-<user> 又被删了。问题不在 /tmp 的清理。
弯路 2:以为 /var/tmp 也被清理了
其实删目录的不是系统,是 codex 自己。
Codex 启动时如果配置加载出错,会用 Rust 的 fs::remove_dir_all() 清理 CODEX_HOME。这个函数遇到软链接时会跟过去把目标目录整个删掉。所以每次 reload,codex 自己一脚把 /tmp/codex-<user>(或 /var/tmp/codex-<user>)的全部内容干掉,然后 ~/.codex 就变成悬空链接,下次启动继续挂。
→ 结论:不能把 ~/.codex 做成软链接。
真正的根因
NFS 锁根本没坏,就是 14:26 那个最早的 codex 进程被卡成了 D state,它一直握着一把 NFS 锁,后面所有 codex 进程去抢同一把锁就排队等,看起来像"NFS lockd 坏了"。杀掉那个僵尸进程后,NFS 锁立刻恢复正常。
顺带两个次要问题:
~/.codex/config.toml里model_reasoning_effort = "xhigh"是无效值,合法值只有minimal/low/medium/high。这是 app-server 后续启动报error loading default config的诱因之一。- VSCode 在 reload 过程中把 Codex 扩展从
26.422.20832自动升级到26.422.21459,但~/.codex/tmp/arg0/codex-arg0*/下还留着指向已删除的旧20832二进制的死链接,codex 启动时rmdir()这些非空目录会 ENOTEMPTY。
最终修复
# 1. 杀掉所有僵尸 codex 进程
pkill -9 -u $USER -f codex
ps -ef | grep codex | grep $USER | grep -v grep # 确认清空
# 2. 撤销软链接,把 ~/.codex 还原为 NFS 上的真实目录
rm -f ~/.codex
mv ~/.codex.nfs-backup-* ~/.codex # 如果之前动过
# 3. 清空 tmp/arg0 里指向已删除旧二进制的死链接
rm -rf ~/.codex/tmp/arg0/*
# 4. 清掉历次失败留下的渣目录
rm -rf ~/.codex.bad.* ~/.codex.bak.* ~/.codex.nfs-backup-*
# 5. 修 config.toml
sed -i 's/"xhigh"/"high"/' ~/.codex/config.toml
# 6. 如果之前搞过本地目录,清掉
rm -rf /tmp/codex-$USER /var/tmp/codex-$USER
验证
timeout 3 flock -n ~/.codex/auth.json -c 'echo ok' # ok
timeout 5 ~/.vscode-server/extensions/openai.chatgpt-*/bin/linux-aarch64/codex --version
timeout 5 ~/.vscode-server/extensions/openai.chatgpt-*/bin/linux-aarch64/codex app-server --analytics-default-enabled </dev/null
# 只报 bubblewrap 路径警告是正常的(会用内置的)
在 VSCode 里按 Ctrl+Shift+P → Developer: Reload Window。
经验总结
-
D state 的进程不会自己退出,但会污染全局锁状态。NFS lockd 对同一文件的请求是排队的,一个被阻塞的 flock 足以让后来所有 flock 都看起来"挂了"。看到
wchan = rpc_wait_bit_killable+/proc/<pid>/fd里有.lock文件,先把僵尸杀了再重新评估,不要直接下"NFS 锁坏了"的结论。 -
不要把带状态的目录做成软链接,尤其是当目标程序是 Rust 写的时候。
fs::remove_dir_all()的语义(跟随软链接删除目标)跟直觉相反,会让软链接方案变成一颗定时炸弹。需要换位置的话,要么用真·bind mount(mount --bind,需要 root),要么看程序本身能不能通过环境变量改路径(Codex 是CODEX_HOME)。 -
cluster 上的
/tmp不一定就是"本地 tmpfs"。在这台 Delta 节点上/tmp是本地 XFS + per-user bind mount,而且有某种 root 跑的清理脚本。想要稳定的本地存储应该查/proc/self/mountinfo确认布局,/var/tmp在这台机器上通常比/tmp更稳。 -
扩展自己的日志永远是第一站。VSCode 扩展在
~/.vscode-server/data/logs/<timestamp>/exthost1/<extensionId>/*.log下都有自己的日志,比从二进制层面猜错误高效得多。 -
相信观察胜过相信结论。一开始那条"NFS lockd 坏了"的假设自洽、证据充分(flock 会超时),但实际上是 observation bias —— 卡住的进程本身就在持锁。每次假设之后要问一句:"这个假设在消除掉已知异常状态后还成立吗?"