跳到主内容
快讯直播
AI智模界
教程

Codex 云环境接力长期任务:环境固化与跨设备续跑

这篇能做出什么

按下面的步骤走一遍,你会得到一套这样的工作方式:

  • 仓库的依赖安装、系统包、环境变量被写进一份可复现的环境定义,每次开新环境自动重建,不需要靠记忆敲命令。
  • 任务跑在云端,进度留在任务本身。换一台电脑、换一个终端,回到同一个任务就能接着往下做,不用把需求重新描述一遍。
  • 包管理器和构建工具的缓存指向持久目录,第二次之后开环境省掉重复下载依赖的时间。

举一个具体例子。一个仓库跑完整测试要先装几百 MB 依赖、再跑六分钟。没有这套做法时,每次换设备都要先等一次安装,人的注意力被打断两次:一次是等装依赖,一次是重新想"我上次做到哪了"。有了这套做法后,新设备上打开同一个任务,直接进入"跑测试 → 改代码 → 再跑测试"的循环。

需要提前说明:不同产品、不同版本在界面上的叫法不一样,有的叫 environment,有的叫 workspace 或 sandbox;字段名和入口位置以官方文档当前版本为准。下面讲的是这套机制的通用做法,落到具体产品时把名字对上即可。

环境固化可以理解成三个层次,由浅到深:

1. 脚本层:一个 setup.sh,别人和新环境都跑它。

2. 声明层:devcontainer.json 或 Dockerfile,把基础镜像、环境变量、安装命令写成文件。

3. 快照层:把装好依赖的容器状态存下来复用,新环境直接从这个状态启动。

三层不是必须全做,但做到第二层,跨设备续跑才有稳定的地基。

前置条件清单

  • 一个 Git 托管仓库,有可读的主分支,且你对该仓库有写权限。
  • 已开通并能使用 Codex 云端任务(具体入口与权限以官方文档当前版本为准)。
  • 本地已经有一份能跑通的最小命令清单:装依赖一条、跑测试一条、跑 lint 一条。
  • 仓库根目录可以新增文件(放 setup.sh、.devcontainer/、AGENTS.md)。
  • 私有依赖源或私有仓库需要的 token 已准备好。token 只放进平台的 secrets 功能,不进仓库。
  • 本地装好 git,能正常 push。云端环境要读到你的脚本,前提是脚本已经在远端分支上。

分步骤

第 0 步:先在本地验证"从零能跑通"

这一步不做,后面所有问题都会混在一起。清空依赖目录,严格按 README 跑一遍:

```bash

记下每一步的耗时,后面判断要不要缓存就靠它

rm -rf node_modules

time npm ci

time npm test

```

把每个命令的耗时抄到便签上。安装 3 分钟、测试 6 分钟,说明安装值得缓存,测试值得并行——优先级一目了然。

如果这步就有报错,先修本地,别指望云端会变好。云端环境不比本地更聪明,它只是更干净。

第 1 步:写一个幂等的 setup 脚本

在仓库里新建 scripts/setup.sh。三个硬性要求:幂等(重复跑不报错)、非交互(不能弹任何提问)、失败即退出。

```bash

#!/usr/bin/env bash

scripts/setup.sh —— 环境初始化,要求幂等、非交互、失败即退出

set -euo pipefail

1) 系统包:先判断存在再装,避免每次重装

if ! command -v jq >/dev/null 2>&1; then

export DEBIAN_FRONTEND=noninteractive

sudo apt-get update

sudo apt-get install -y jq

fi

2) 缓存目录指向工作区内路径,重建容器也能复用(详见第 3 步)

export NPM_CONFIG_CACHE="${NPM_CONFIG_CACHE:-/workspaces/.cache/npm}"

export PIP_CACHE_DIR="${PIP_CACHE_DIR:-/workspaces/.cache/pip}"

mkdir -p "$NPM_CONFIG_CACHE" "$PIP_CACHE_DIR"

3) Node 依赖:必须用锁定文件做冻结安装

if [ -f package-lock.json ]; then

npm ci

elif [ -f pnpm-lock.yaml ]; then

corepack enable

pnpm install --frozen-lockfile

elif [ -f yarn.lock ]; then

corepack enable

yarn install --immutable

fi

4) Python 依赖

if [ -f requirements.txt ]; then

python -m pip install --upgrade pip

python -m pip install -r requirements.txt

fi

echo "setup 完成"

```

几个容易写错的点:

  • npm install 会就地解析版本,同样代码在不同环境可能装出不同依赖树。npm ci、pnpm install --frozen-lockfile 这类冻结安装才能保证一致。前提是锁定文件已经提交到仓库。
  • 所有安装命令都要加非交互参数。apt-get 用 -y 且设 DEBIAN_FRONTEND=noninteractive,pip 不要省略 --upgrade pip 之外的交互。
  • 把耗时长的步骤放在脚本后面,日志定位更快。

写完给脚本执行权限,并在本地跑两遍验证幂等:

```bash

chmod +x scripts/setup.sh

bash scripts/setup.sh

bash scripts/setup.sh # 第二遍也必须成功退出

```

第 2 步:把环境写进声明文件

脚本只能管"装什么",管不了"在哪装"。基础镜像、工作目录、环境变量这些要写进声明文件。

Codex 云端环境通常有一个配置入口,可以填 setup 脚本、环境变量和密钥。更方便的做法是把这些写进仓库里的 devcontainer.json 或 Dockerfile,这样环境定义跟着代码走,谁都能看见、能评审、能改。

```json

{

"name": "repo-task",

"image": "<基础镜像,按官方文档当前版本选择并固定>",

"postCreateCommand": "bash scripts/setup.sh",

"containerEnv": {

"NPM_CONFIG_CACHE": "/workspaces/.cache/npm",

"PIP_CACHE_DIR": "/workspaces/.cache/pip",

"TZ": "Asia/Shanghai",

"LANG": "C.UTF-8"

},

"remoteUser": "vscode"

}

```

镜像尽量固定到一个明确的标识,不要用 latest 这类会漂移的标签,否则今天能跑、下周挂掉,排查起来会很痛苦。

密钥处理:环境变量分两类。一类是普通配置(缓存路径、时区、locale),直接写在文件里;另一类是凭证(私有源的 token、API key),只写变量名,值通过平台的 secrets 功能注入:

```bash

在 setup 脚本里按需读取,值由平台注入,脚本里不出现明文

if [ -n "${PRIVATE_REGISTRY_TOKEN:-}" ]; then

npm config set //your.registry.example/:_authAI 词典:Token">Token "${PRIVATE_REGISTRY_TOKEN}"

fi

```

把 .env、.cache/、node_modules/ 加进 .gitignore,别让缓存和密钥混进提交。

固化到什么程度可以算验收通过?新建一个干净环境,只执行 setup 脚本,测试能跑绿。 这条标准比任何描述都可靠。

第 3 步:让缓存真正被复用

这是很多人以为做了、其实没生效的一步。

原理很简单:npm、pip、cargo、go、maven 的默认缓存目录都在 $HOME 下面。如果每次都是全新容器,$HOME 里什么都没有,缓存自然等于不存在。把缓存目录指到会被持久化的位置,复用才发生。

```bash

统一放到工作区下的 .cache,并在 .gitignore 里排除

export NPM_CONFIG_CACHE=/workspaces/.cache/npm

export PIP_CACHE_DIR=/workspaces/.cache/pip

export CARGO_HOME=/workspaces/.cache/cargo

export GOMODCACHE=/workspaces/.cache/go/pkg/mod

export MAVEN_OPTS="-Dmaven.repo.local=/workspaces/.cache/maven"

```

不同产品的持久化范围不同:有的会保留整个工作区,有的提供容器快照功能,有的两者都有。以官方文档当前版本为准,先确认哪块目录能留到下次,再把缓存变量指过去。指错地方不报错,只是悄悄重新下载,所以一定要用"第二次开环境是否还下载依赖"来验证。

缓存失效的三种正常情况,遇到不用慌:

  • 锁定文件改了(依赖本来就变了)。
  • 基础镜像换了。
  • 系统包大版本升级。

这三种情况重新下载是应该的。真正要警惕的是"什么都没改却每次全量下载",那说明缓存路径指错了。

第 4 步:把任务搬进云端,跑第一次

在 Codex 里针对这个仓库开一个任务。任务描述要包含四件事:做什么、约束是什么、怎么验证、产物放哪。

一段可以直接改的模板:

```text

目标:把 src/parser 模块的测试补齐,直到 npm test 全绿。

约束:

  • 不要修改公共 API 的函数签名
  • 不要升级任何依赖的大版本
  • 不改动 CI 配置文件

验证命令:

  • 单文件:npm test -- src/parser/lexer.test.ts
  • 全量:npm test

产物:一个可以提交的 PR,改动说明写在 PR 描述里。

如果 setup 脚本报错,先停下把日志贴出来,不要绕过。

```

第一次运行重点看两段日志:setup 阶段的日志(环境问题都在这)、测试阶段的日志(代码问题在这)。这两类问题的排查方向完全不同,先分清再动手。

第 5 步:把"进度"写进仓库,而不是记在脑子里

跨设备续跑的关键不在工具,在于任务上下文存在哪儿。如果上下文只存在于你上一次的对话里,换设备就断了。

做法是在仓库里放一份任务上下文文件,常见名字是 AGENTS.md 或 docs/task.md,让每个新环境开局都能读到同样的信息:

```markdown

任务上下文

目标

补齐 src/parser 模块测试,保持 CI 全绿。

环境

  • 初始化:bash scripts/setup.sh
  • 全量测试:npm test
  • 单文件测试:npm test -- <path>
  • 代码检查:npm run lint

约束

  • 不改公共 API 签名
  • 不升级依赖大版本

当前进度

  • [x] lexer.test.ts 补齐
  • [x] parser.test.ts 补齐
  • [ ] ast.test.ts(下一步,先看 3 个 skip 的用例)
  • [ ] 全量测试跑绿

验收

  • npm test 全绿
  • npm run lint 无新增告警

```

于是"接力"变成三个动作,每个环境、每台设备都一样:

1. 读进度:让 agent 先读 AGENTS.md 的"当前进度"和"下一步"。

2. 跑验收:执行验证命令,确认起点是绿的,再开始改。

3. 更新进度:这一轮结束,把勾选状态和"下一步"改掉,提交。

第 2 步特别容易被跳过。不确认起点状态就动手,最后分不清是新改动引入的问题还是本来就坏着。

回到同一个任务时,不要重新描述一遍需求。同一个任务里的历史、文件改动、日志都在,重新描述等于把上下文丢掉一半。换设备后要做的是打开那个任务,然后执行上面三步。

第 6 步:切阶段,别一口气跑完

长期任务不适合一次提示做完。按验收命令能跑通的粒度切阶段,每阶段结束就提交一次。好处有三个:中断了不用重来、每步都有可回退点、进度文件能持续更新。

阶段之间如果环境有了新变化(比如新加了一个系统依赖),记得回头改 setup.sh,别只在当前容器里手动装。手动装的那一份,下一个环境就没有了——这正是"环境没固化"的典型症状:当前环境能用,新环境不能用。

常见坑与排错

setup 一直卡住不动。 九成是脚本里有交互式提示在等输入。所有安装命令加非交互参数,apt-get 加 -y 并设置 DEBIAN_FRONTEND=noninteractive。

日志里提示找不到锁定文件。 用了 npm ci 但仓库里没有 package-lock.json。先在本地生成并提交锁定文件,再上云端。

依赖每次重新下载。 缓存变量指向了 $HOME 下的默认位置,而 $HOME 不持久。改成工作区内的路径,然后开第二个环境验证是否还下载。

出现了"本地能跑、云端不能跑"。 先清缓存重跑一次,把缓存因素排除掉。如果清了还不行,就是环境差异:系统包版本、locale、时区、字体都可能。把这些统一写进声明文件。

私有源拉不动。 检查三件事:token 是否通过 secrets 注入、registry 地址是否配在固化配置里(而不是只在当前 shell 里 npm config set)、证书是否需要在镜像里预装。

工作区被撑满。 node_modules、构建产物、缓存三样叠起来很占空间。缓存和构建产物尽量放到工作区外的临时目录,或者定期清理。判断标准是"删了能不能重建"——能重建的就别当宝贝留着。

换设备后进度对不上。 说明上一轮的进度没写回仓库。养成习惯:阶段完成的最后一个动作是更新 AGENTS.md 并提交,而不是关掉窗口。

测试偶发失败。 时区、locale、并行度、随机种子都可能。先把这些在固化配置里统一(TZ、LANG),再看是不是真 bug。不要靠"重跑一次就过了"糊过去。

改了 setup 脚本但环境没变。 确认改动已经 push 到远端分支。云端环境读的是远端仓库,本地未提交的改动它看不见。

下一步建议

  • 把 setup.sh 和 devcontainer.json 纳入代码评审流程,改它们和改业务代码一个待遇。环境漂移的成本,通常在出问题那天才被看见。
  • 给环境加一个自检脚本,新环境起来先跑一遍:检查关键命令是否存在、依赖目录是否就位、测试基础套件是否通过。几十秒换一个明确的起点,划算。
  • 把常用命令做成 make 目标或脚本,减少记忆负担和手敲错误:

```makefile

setup:

bash scripts/setup.sh

test:

npm test

check: test

npm run lint

resume:

@cat AGENTS.md | sed -n '/当前进度/,/验收/p'

```

make resume 这一条尤其省事:换设备第一件事就是它,两秒钟把"上次做到哪"读回来。

  • 观察 setup 的耗时趋势。如果稳定在几分钟且内容长期不变,考虑把不变的部分固化进自定义镜像,只把变动部分留给脚本。
  • 多个任务并行时留意缓存的并发写入。几个任务同时往同一个缓存目录写,偶发损坏是可能的。如果遇到诡异失败,先换一个独立缓存目录复现,判断是不是这个原因。
  • 每隔一段时间回看一次:是不是还有"只在当前环境手动装过、没写进脚本"的东西。有就立刻补回去,这就是环境固化这件事的全部日常。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。