把 Codex Home 从隐藏目录搬出来,用 Git 管理 AI 工作流资产

最近开始认真考虑一个问题:Codex 里沉淀下来的东西,应该怎么长期保存?

一开始,做法很朴素:把一些重要目录打成 zip,上传到云盘。这能兜住「电脑坏了怎么办」「换设备怎么办」这类问题,但不适合长期维护:

  • 每次备份都是一个新的压缩包,不容易看差异。
  • 只知道「备份了」,但不知道具体变了什么。
  • 想回到某个历史版本,需要先下载、解压、人工比较。
  • 想在多台设备之间同步,也不够自然。

压缩包更像一个快照,不像一个持续演进的工作区。后来又回到了一个很老但很好用的答案:Git。

先分清资产和运行态

Codex 的本地状态通常在 ~/.codex 下面。这个目录有点像一个 AI 工具的用户主目录:里面既有真正值得保存的长期资产,也有大量只属于当前机器、当前登录态和当前进程的运行状态。

值得保存的是可读、可审查、可迁移的工作流资产:

codex-home/
  AGENTS.md
  global/AGENTS.md
  agents_references/
  rules/
  skills/
  tools/
  env/

global/AGENTS.md 是 Codex 全局运行规则,比如希望 Codex 用什么语气、怎么协作、遇到某类任务先看什么引用。

agents_references/ 放全局规则引用的细节文档。全局入口只保留触发条件和路径,长规则拆到这里,能减少每次会话一上来就读进大量细节。

rules/ 是更细的命令或执行规则,适合长期稳定、跨任务复用的边界。

skills/ 是最重要的一类。Skill 会把一套可复用流程固化下来,让 Codex 下次遇到类似任务时不用从零开始理解。比如文档整理流程、某类调试流程、某个工具链的操作规范,都可以变成 Skill。

tools/ 放可复用的小工具、脚本或 MCP server 源码。这里保存源码、README、lockfile 和必要配置,不保存依赖安装目录和运行缓存。

env/ 只适合私有仓库。它可以保存内部工具需要的环境变量文件,但不适合作为公开模板直接分发。如果仓库会公开,应该改成 .env.example 或文档说明,真实 env 留在本机。

这些内容的共同点是:它们能被人读懂,也能通过 Git 看到演进历史。

不该提交的是运行态:

auth.json
config.toml
sessions/
archived_sessions/
browser-profiles/
cache/
plugins/cache/
tmp/
.tmp/
*.sqlite
*.sqlite-shm
*.sqlite-wal
logs/
worktrees/
generated_images/
shell_snapshots/
node_modules/
.venv/
__pycache__/

auth.jsonbrowser-profiles/ 可能包含登录态或凭证,不能进 Git。sessions/archived_sessions/ 体积会越来越大,也可能包含大量上下文和内部信息。*.sqlitecache/.tmp/logs/ 都强依赖当前机器和当前版本,同步到另一台设备上价值不大,风险不少。

这里的边界很简单:Git 保存的是「可迁移能力」,不是「运行现场」。

为什么不再把整个 ~/.codex 变成仓库

最直接的想法是:

cd ~/.codex
git init

然后配一个白名单 .gitignore,默认忽略所有内容,只放行明确要同步的文件。这个方案能工作,但长期维护会有两个问题。

第一个问题是隐藏目录不顺手。想经常用编辑器打开它,看看 Skill 写了什么,检查某条规则有没有过期,隐藏目录当然也能打开,但不如放在一个明确的代码目录里自然。

第二个问题更实际:~/.codex 不是纯源码目录。Codex 会持续往里面写登录态、sqlite、session、缓存、插件缓存、浏览器 profile 和临时状态。哪怕 .gitignore 很严格,人也会一直站在一个运行态目录里维护长期资产,心智负担会变高。

早期我也试过另一种更粗的做法:把 ~/.codex 整个迁到 ~/code/codex-home,再让 ~/.codex 指向它:

~/.codex -> ~/code/codex-home

这比直接在隐藏目录里维护舒服,但它仍然把运行态和可迁移资产放在同一个真实目录里。维护一段时间后,更清楚的做法是保留 ~/.codex 作为运行目录,只把需要长期维护的文件和目录软链出去。

现在的结构:运行态留在 ~/.codex

现在推荐的是细粒度软链。

真实仓库放在日常代码目录:

~/code/codex-home/

Codex 继续使用默认运行目录:

~/.codex/

两边通过少数软链接连起来:

~/.codex/AGENTS.md          -> ~/code/codex-home/global/AGENTS.md
~/.codex/agents_references  -> ~/code/codex-home/agents_references
~/.codex/rules              -> ~/code/codex-home/rules
~/.codex/skills             -> ~/code/codex-home/skills
~/.codex/env                -> ~/code/codex-home/env
~/.codex/tools              -> ~/code/codex-home/tools

这样,Codex 仍然从熟悉的位置读取规则、Skill 和工具;人维护的是一个普通 Git 仓库。登录态、会话、sqlite、浏览器 profile、插件缓存和临时目录继续留在 ~/.codex,不跟着仓库漂移。

这也是为什么现在不再推荐把整个 ~/.codex 都软链出去。整目录软链解决了「目录不好找」的问题,但没有真正拆开「长期资产」和「运行现场」。

skills/ 有一个小例外,值得单独说一下。Codex 可能会在 ~/.codex/skills/.system/ 下放系统生成的 Skill,这些内容不是我手写的工作流资产,也不应该提交到自己的仓库。

如果只把自己写的 Skill 一个个链接进去,通常碰不到它;如果为了省心,把整个 skills/ 目录链接到仓库里,.system/ 就可能出现在仓库工作区里。这个时候不需要把整个方案推倒,只要在仓库 .gitignore 里忽略:

skills/.system/

换句话说,真正需要隔离的是系统生成内容。它和人工维护内容刚好落在同一个目录层级;把 .system/ 当成运行态处理,仍然可以保留整个 skills/ 目录链接的便利。

AGENTS.md 要拆成两层

把 Codex 资产放进一个仓库后,会遇到一个容易忽略的歧义:仓库根目录也可以有 AGENTS.md,而 Codex 自己的 home 目录也有 AGENTS.md

如果仓库根目录的 AGENTS.md 既被当成 Codex 全局运行规则,又被当成 codex-home 这个仓库的项目说明,读起来会很别扭。人在维护仓库时看到它,会以为这是仓库规则;Codex 在别的项目里读到它时,又应该把它理解成全局协作规则。两种语义混在同一个文件里,后面一定会误解。

更清楚的分法是:

codex-home/
  AGENTS.md          # 只描述这个仓库怎么维护、怎么迁移
  global/AGENTS.md   # Codex 全局运行规则

然后让 Codex home 里的入口指向全局规则:

~/.codex/AGENTS.md -> ~/code/codex-home/global/AGENTS.md

这样,根 AGENTS.md 只回答仓库问题:这个仓库保存什么、不保存什么、新机器怎么链接、提交前怎么检查。global/AGENTS.md 只回答 Codex 行为问题:默认语言、协作方式、遇到 Figma 或浏览器任务时读什么 Skill。

global/AGENTS.md 里引用细节文档时,路径也应该写成 Codex home 视角:

${CODEX_HOME:-$HOME/.codex}/agents_references/browser-channel.md

不要写成简单的 agents_references/browser-channel.md。全局规则会在很多项目里生效,引用路径必须能从 Codex home 找到,而不是依赖当前工作目录刚好是 codex-home

当前机器怎么迁移

如果一台机器已经在使用 Codex,迁移时不要一上来就动登录态和 session。更稳的顺序是:

  1. 在日常代码目录里创建或 clone 私有仓库。
  2. 把长期资产整理进仓库。
  3. 备份 ~/.codex 里同名文件或目录。
  4. 用软链接把仓库里的资产挂回 ~/.codex
  5. 启动 Codex,确认规则和 Skill 能正常读取。
  6. 最后再提交和推送仓库。

准备仓库目录这一步仍然可以手动做:

mkdir -p ~/code/codex-home/global
cd ~/code/codex-home
git init
git branch -M main

再把已有资产放进仓库。不同机器的情况不一样,下面只表达路径关系,真正执行前先看清自己本机是否已有对应目录:

cp ~/.codex/AGENTS.md ~/code/codex-home/global/AGENTS.md
cp -R ~/.codex/rules ~/code/codex-home/rules
cp -R ~/.codex/skills ~/code/codex-home/skills

如果已经有 agents_references/tools/ 或私有 env/,也可以按同样方式放进仓库。没有就先不创建空目录。

真正把仓库挂回 Codex home 时,现在更推荐走一个跨平台入口:

node tools/setup-codex-home.mjs --repo "$HOME/code/codex-home" --dry-run
node tools/setup-codex-home.mjs --repo "$HOME/code/codex-home"

Windows PowerShell 里仍然是同一个脚本,只是路径写法不同:

node .\tools\setup-codex-home.mjs --repo "$HOME\Desktop\code\codex-home" --dry-run
node .\tools\setup-codex-home.mjs --repo "$HOME\Desktop\code\codex-home"

--dry-run 只打印计划,不改文件。确认它会备份哪些旧路径、创建哪些链接以后,再跑真实命令。

这个脚本内部做几件事:

  • 已经是正确链接时直接跳过。
  • 已有普通文件或错误链接时,先备份成 .local-backup.<timestamp>
  • 缺失的可选目录会跳过,不为了凑结构创建空目录。
  • AGENTS.mdagents_referencesrulesskillsenvtools 都按同一份映射关系处理。
  • skills/.system/ 继续按本机运行态处理,只保留在工作区并被 .gitignore 忽略。

迁移成功后做几个检查:

test -f ~/.codex/skills/skill-creator/SKILL.md
git -C ~/code/codex-home ls-files | rg '(^|/)(auth|sessions|browser-profiles|cache|tmp|logs|worktrees|generated_images|.*\.sqlite)'

第一条用来确认 Skill 可读。第二条用来确认 Git 没有跟踪运行态或敏感文件;正常情况下应该没有输出。链接本身是否正确,脚本最后也会打印 verify 结果。

平台差异收进脚本里

跨平台方案的核心是同一个脚本入口。脚本内部按平台选择正确的文件系统原语。

macOS 和 Linux 上,文件、目录都可以走普通 symlink。Windows 上要更细一点:目录优先用 junction;文件优先用 symbolic link,如果当前权限不允许创建文件符号链接,再退到同盘 hard link。

原生 Windows 路线现在是可以成立的。Codex 官方文档里,Windows 桌面应用和 CLI 可以在 PowerShell 里配合 Windows sandbox 使用;不需要为了 Codex 本身强行套一层虚拟机。真正要考虑的是链接类型、权限和本机文件系统。

Git Bash 适合日常跑很多类 Unix 命令,但它没有改变底层文件系统。把 macOS 那段 ln -s 原样交给 Git Bash,最后生成什么仍然会受到 Windows 权限、MSYS/Git 配置和目标类型影响。迁移这种长期动作,交给 Node 脚本显式分支更稳。

Windows 上可以这样检查:

Get-Item "$HOME\.codex\AGENTS.md" | Format-List FullName,LinkType,Target
Test-Path "$HOME\.codex\skills\skill-creator\SKILL.md"
git -C $Repo ls-files | rg 'auth|session|sqlite|browser-profiles|cache|tmp|logs|worktrees|generated_images|node_modules|\.venv'

如果 AGENTS.md 最后是 hard link,LinkType 不一定像 symbolic link 那样直观;这时重点看两边内容是否同步、Git 是否只跟踪仓库里的那份文件。

那 WSL 合适吗

WSL2 更像一条 Linux 运行路线,适合工具链本来就在 Linux 的工作流。

如果你的项目、本地工具链、包管理器、测试脚本本来就更偏 Linux,或者原生 Windows sandbox 在当前公司设备策略下跑不顺,Codex 的 WSL 文档 也建议选择 WSL2。这个时候就不要把仓库放在 /mnt/c/... 里来回跨文件系统访问,直接放在 WSL 的 home 目录,比如:

mkdir -p ~/code
git clone <private-repo-url> ~/code/codex-home

WSL 里也有自己的 ~/.codex。如果 Windows 原生 Codex 和 WSL 里的 Codex 都要用同一套资产,我会让两边各自 clone 同一个私有仓库,再用 Git 同步,而不是让 Windows 的 ~/.codex 和 WSL 的 ~/.codex 互相指。这样边界更清楚:Windows 运行态留在 Windows,WSL 运行态留在 Linux,真正共享的是 Git 里的工作流资产。

在另一台机器 fork 或 clone

把这个仓库拆出来,真正的价值就在这里:换设备时不用搬整个 ~/.codex,只要 clone 私有仓库,再把可迁移资产链接回 Codex home。

新机器上先让 Codex 自己创建默认目录,或者手动准备空目录:

mkdir -p ~/.codex

然后 clone 私有仓库:

git clone <private-repo-url> ~/code/codex-home

再运行跨平台脚本:

cd ~/code/codex-home
node tools/setup-codex-home.mjs --repo "$PWD" --dry-run
node tools/setup-codex-home.mjs --repo "$PWD"

如果新机器已有自己写过的 Skill 或规则,不要直接删备份目录。先把确实要保留的内容合并进 ~/code/codex-home,再删除 .local-backup.<timestamp>

链接完成后,再执行登录或打开桌面应用:

codex login

Codex 会在新机器上重新生成登录态、sqlite、session、插件缓存、浏览器 profile 和其他运行态。这是预期行为。新机器真正继承的是你的工作方式和可复用能力,而不是旧机器的所有运行痕迹。

为什么主入口改成 Node

Shell 示例很适合解释路径关系,但这套流程要长期在 macOS、Windows 和 WSL 之间复用,主入口就不应该绑定某个 shell。

这里我会偏向 Node。Node 的 fsPromises.symlink(target, path, type) 明确支持 Windows 上的 filedirjunction 类型,官方文档 也写清楚了 junction 只能指向目录,并且目标路径会被规范成绝对路径。做目录链接时,可以把 Windows 分支写得很直接:

import { symlink } from 'node:fs/promises';

const type = process.platform === 'win32' ? 'junction' : undefined;
await symlink(targetDir, linkPath, type);

Python 标准库也能做跨平台路径处理,pathlibos.symlinkos.link 都能覆盖一部分需求。但 Windows junction 这件事,Python 标准库没有 Node 那种直接的 type: "junction" 参数;如果要做得完整,通常还要额外调用 Windows 命令、PowerShell,或者封装 Win32 API。不是不能做,只是对这个场景来说,Node 更省心。

无论用哪种语言,脚本都应该按「可重复执行」设计:正确链接不动,错误链接先备份,缺失目录跳过或创建明确需要的父目录,任何凭证、session、sqlite、缓存都不搬。

这个选型本身也值得单独写一篇。更完整的判断,我放到了把 Codex Home 迁移做成跨平台 Node 脚本

迁移后要扫掉旧机器绝对路径

还有一类问题和链接无关,但迁移到多设备后很容易暴露:Skill、规则或工具脚本里写死了旧机器路径。

我会在迁移后扫一遍:

rg -n '/Users/|/home/|C:\\Users\\' AGENTS.md global agents_references rules skills tools env

扫出来不代表一定要改。比如某篇文档里为了举例写了 /Users/alice/...,可以保留。但如果是全局规则、Skill 触发说明或工具脚本里的真实工作路径,就应该尽量换成环境变量或相对稳定的入口:

${CODEX_HOME:-$HOME/.codex}
${CODE_DIR:-$HOME/Desktop/code}
${SUGO_WEB_REPO}
${FRONTEND_AI_KNOWLEDGE_REPO}

这样仓库迁到 Windows、macOS 或 WSL 时,真正需要改的是本机 env,而不是到处翻 Skill 和脚本。

私有仓库也要检查边界

远端仓库是私有的,不代表可以把所有东西都推上去。私有仓库降低了公开泄露风险,但不能消除误提交凭证、登录态和大体积运行缓存的风险。

我会把仓库分成三类:

  • 可以提交global/AGENTS.mdagents_references/rules/skills/、可复用工具源码、README、lockfile。
  • 私有仓库才可以提交:内部工具 env、只给自己使用的私有 Skill、带个人工作流偏好的全局规则。
  • 不要提交auth.jsonconfig.toml、session、浏览器 profile、sqlite、日志、缓存、插件缓存、依赖安装目录、构建产物。

提交前至少做两件事:

git status --short
git ls-files | rg 'auth|session|sqlite|browser-profiles|cache|tmp|logs|worktrees|generated_images|node_modules|\.venv'

第一条看本次提交会包含什么,第二条看 Git 已经跟踪了什么。第二条只要有可疑输出,就先停下来检查。

这个方案解决什么

这个方案不是为了「备份一个目录」。

它解决的是:把 AI 工具的个人工作流资产,从一个隐藏的运行状态目录里拿出来,变成一个可读、可审查、可版本管理、可迁移的仓库。

最终结构是:

~/code/codex-home/          # 私有 Git 仓库,方便人维护
~/.codex/                   # Codex 运行目录,保留登录态和运行现场

~/.codex/AGENTS.md          -> ~/code/codex-home/global/AGENTS.md
~/.codex/agents_references  -> ~/code/codex-home/agents_references
~/.codex/rules              -> ~/code/codex-home/rules
~/.codex/skills             -> ~/code/codex-home/skills
~/.codex/env                -> ~/code/codex-home/env
~/.codex/tools              -> ~/code/codex-home/tools

压缩包适合兜底,Git 适合长期演进,细粒度软链接适合兼顾工具默认路径和人的维护体验。

这样一来,Codex 还是那个 Codex;只是它的长期能力终于有了一个清楚、可见、能版本管理的家。