Skip to content

本文除此说明外全部内容均为 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 二进制

find ~/.vscode-server/extensions -maxdepth 4 -name 'codex' 2>/dev/null

路径:~/.vscode-server/extensions/openai.chatgpt-<version>-linux-arm64/bin/linux-aarch64/codex

3. 直接运行二进制,复现问题

timeout 8 <codex-binary> --version
# 卡住 → exit 124

--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 做成软链接

真正的根因

# 杀掉僵尸进程之后重测
timeout 3 flock -n ~/.codex/auth.json -c 'echo ok'    # ok

NFS 锁根本没坏,就是 14:26 那个最早的 codex 进程被卡成了 D state,它一直握着一把 NFS 锁,后面所有 codex 进程去抢同一把锁就排队等,看起来像"NFS lockd 坏了"。杀掉那个僵尸进程后,NFS 锁立刻恢复正常。

顺带两个次要问题:

  1. ~/.codex/config.tomlmodel_reasoning_effort = "xhigh" 是无效值,合法值只有 minimal/low/medium/high。这是 app-server 后续启动报 error loading default config 的诱因之一。
  2. 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+PDeveloper: Reload Window

经验总结

  1. D state 的进程不会自己退出,但会污染全局锁状态。NFS lockd 对同一文件的请求是排队的,一个被阻塞的 flock 足以让后来所有 flock 都看起来"挂了"。看到 wchan = rpc_wait_bit_killable + /proc/<pid>/fd 里有 .lock 文件,先把僵尸杀了再重新评估,不要直接下"NFS 锁坏了"的结论。

  2. 不要把带状态的目录做成软链接,尤其是当目标程序是 Rust 写的时候。fs::remove_dir_all() 的语义(跟随软链接删除目标)跟直觉相反,会让软链接方案变成一颗定时炸弹。需要换位置的话,要么用真·bind mount(mount --bind,需要 root),要么看程序本身能不能通过环境变量改路径(Codex 是 CODEX_HOME)。

  3. cluster 上的 /tmp 不一定就是"本地 tmpfs"。在这台 Delta 节点上 /tmp 是本地 XFS + per-user bind mount,而且有某种 root 跑的清理脚本。想要稳定的本地存储应该查 /proc/self/mountinfo 确认布局,/var/tmp 在这台机器上通常比 /tmp 更稳。

  4. 扩展自己的日志永远是第一站。VSCode 扩展在 ~/.vscode-server/data/logs/<timestamp>/exthost1/<extensionId>/*.log 下都有自己的日志,比从二进制层面猜错误高效得多。

  5. 相信观察胜过相信结论。一开始那条"NFS lockd 坏了"的假设自洽、证据充分(flock 会超时),但实际上是 observation bias —— 卡住的进程本身就在持锁。每次假设之后要问一句:"这个假设在消除掉已知异常状态后还成立吗?"