第一个 Codex 任务:关在沙箱里,让它从零建一个 API


上次用 Claude Code 改了一个三个月没动的 Python 脚本,六分钟提交。那是对老项目动小手术。

这次换个场景——从零开始。

我要一个 Todo API,Flask 写的,能增删改查,数据存 SQLite。以前这种"新建项目"我都是手写脚手架,复制粘贴老项目的骨架,删掉不需要的部分,改改配置,然后才开始写逻辑。

这次全部丢给 Codex,看看行不行。


第一件事:装 Codex

npm install -g @openai/codex

也可以用 Homebrew:

brew install codex

装完认证:

codex login

浏览器弹出来,登录 ChatGPT 账号(Plus/Pro/Team 订阅都行)。如果你在服务器上没有浏览器,用设备码方式:

codex login --device-auth

终端会显示一个短码,在另一台设备的浏览器里输入就行。用 API Key 的话:

echo $OPENAI_API_KEY | codex login --with-api-key

确认装好了:

$ codex --version
0.x.x

需求

在空目录里,直接告诉 Codex 我要什么:

$ codex exec "创建一个 Flask todo API,功能:
   - GET /todos — 返回所有 todo 列表
   - POST /todos — 添加一个 todo(字段:title, completed)
   - PUT /todos/<id> — 更新一个 todo
   - DELETE /todos/<id> — 删除一个 todo
   数据存 SQLite,用 Flask-SQLAlchemy。
   加上 pytest 测试。"

Codex 启动后的行为跟 Claude Code 完全不同——它先探路,再动手:

→ Sandbox: workspace-write
→ Exploring: ls, cat (no existing files — empty directory)
→ Tool: update_plan
   1. Create Flask app structure (app.py, requirements.txt)
   2. Set up SQLAlchemy model for Todo
   3. Implement CRUD routes
   4. Create pytest test suite
   5. Verify all endpoints
→ Executing plan...

它在一个沙箱里干活。macOS 上用的是 Apple Seatbelt 内核级沙箱——所有文件操作都在当前目录和临时目录内,网络默认切断。


看它一步步建项目

第一步,建文件结构:

→ Tool: apply_patch
   [+] requirements.txt
       flask==3.1.*
       flask-sqlalchemy==3.1.*
       pytest==8.*
   [+] app.py (52 lines)
       - Flask app factory
       - SQLAlchemy init with SQLite
       - Todo model (id, title, completed, created_at)
       - CRUD routes with error handling
   [+] test_app.py (68 lines)
       - pytest fixtures for test client and temp DB
       - test_create_todo
       - test_get_todos
       - test_update_todo
       - test_delete_todo
       - test_404_on_missing_todo

第二步,装依赖:

→ Tool: shell
   $ pip install flask flask-sqlalchemy pytest
   Collecting flask==3.1.* ...
   Successfully installed ...

第三步,跑测试:

→ Tool: shell
   $ python -m pytest test_app.py -v

   test_create_todo ...... PASSED
   test_get_todos ........ PASSED
   test_update_todo ...... PASSED
   test_delete_todo ...... PASSED
   test_404_on_missing ... PASSED

   5 passed in 0.18s

第四步,实际跑起来验证:

→ Tool: shell
   $ python app.py &
   $ curl -s http://localhost:5000/todos
   []
   $ curl -s -X POST http://localhost:5000/todos \
     -H 'Content-Type: application/json' \
     -d '{"title": "试用 Codex"}'
   {"id": 1, "title": "试用 Codex", "completed": false}
   $ curl -s http://localhost:5000/todos
   [{"id": 1, "title": "试用 Codex", "completed": false}]

五个测试全过。curl 实测也正常。


确认:一键落地

所有变更在一个原子 patch 里:

→ Ready to apply:
   [+] requirements.txt (4 lines)
   [+] app.py (52 lines)
   [+] test_app.py (68 lines)

   Apply changes to workspace? [Y/n]

我按下 Y。三个文件落到本地目录。完整的一个 Todo API,带测试,零行手写。

从执行指令到文件落地,三分钟出头。其中大部分时间它在沙箱里自己干活,我只需要在最后按一下 Y。


实际生成的代码

app.py——Codex 写的完整代码:

from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todos.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

class Todo(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    completed = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    def to_dict(self):
        return {
            'id': self.id,
            'title': self.title,
            'completed': self.completed,
            'created_at': self.created_at.isoformat()
        }

@app.route('/todos', methods=['GET'])
def get_todos():
    todos = Todo.query.all()
    return jsonify([t.to_dict() for t in todos])

@app.route('/todos', methods=['POST'])
def create_todo():
    data = request.get_json()
    if not data or 'title' not in data:
        return jsonify({'error': 'title is required'}), 400
    todo = Todo(title=data['title'],
                completed=data.get('completed', False))
    db.session.add(todo)
    db.session.commit()
    return jsonify(todo.to_dict()), 201

@app.route('/todos/<int:todo_id>', methods=['PUT'])
def update_todo(todo_id):
    todo = Todo.query.get(todo_id)
    if not todo:
        return jsonify({'error': 'todo not found'}), 404
    data = request.get_json()
    if 'title' in data:
        todo.title = data['title']
    if 'completed' in data:
        todo.completed = data['completed']
    db.session.commit()
    return jsonify(todo.to_dict())

@app.route('/todos/<int:todo_id>', methods=['DELETE'])
def delete_todo(todo_id):
    todo = Todo.query.get(todo_id)
    if not todo:
        return jsonify({'error': 'todo not found'}), 404
    db.session.delete(todo)
    db.session.commit()
    return jsonify({'message': 'deleted'}), 200

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

看几个细节:

  • to_dict() 抽在 Model 上,不是每个路由里手写 dict
  • POST 做了字段校验,title 不传就 400,不是裸收用户输入
  • PUT 是部分更新——只动传了的字段
  • 每个按 ID 查的端点都处理了 404
  • 启动自动建表,拿过来就能跑

不像样板代码。有人味。


Codex 的沙箱,跟 Claude Code 的本地执行有什么不同

两个工具都跑过了,最直观的对比:

Claude Code Codex
在哪干活 你的终端,直接操作项目文件 OS 级沙箱,内核强制隔离
能上网吗 能,完全自由 默认不能,得主动授权
装依赖 直接 pip install 到你的环境 沙箱内安装,通过后才落地
文件怎么落地 边写边改,直接生效 原子 patch,确认后一次性写入
出错了 你的文件直接被改了 沙箱内的错误不会污染本地

Codex 这个沙箱设计,对两件事特别友好:

第一次用不放心。 你不知道 AI 会写出什么东西,不想让它直接碰你的代码。沙箱给了你一个观察窗——看着它干活,干完了再决定要不要。

从零建新项目。 新项目没有"历史包袱",Codex 在沙箱里自己搭骨架、跑测试、装依赖,你确认没问题后一键落地。不需要你先手写脚手架再让它改。

反过来,Claude Code 的本地执行更适合"在老项目里加功能"——它能看到项目的全部上下文,改完直接生效,不用最后才同步。


踩到的坑

1. 网络默认是关的

如果任务需要访问外部 API,Codex 会提示网络权限不够。需要切换到完整访问模式:

codex exec --sandbox danger-full-access "查一下 PyPI 上最新的 flask..."

或者在 session 里通过审批流程临时授权。

2. 懒加载:它不会主动扫你的项目

Codex 启动时只读 AGENTS.md。如果你的项目已经有代码,它不会自动发现。需要先跑 /init 命令生成一个 AGENTS.md,或者用 /mention 显式告诉它哪些文件需要关注:

/mention app.py models.py utils.py

否则它只看到你告诉它的那部分。50 个文件的项目,如果不做这一步,它可能完全不知道 utils.py 里有个现成的辅助函数。

3. patch 粒度问题

Codex 把多个文件改动用 apply_patch 打包成一个原子操作。好处是避免半成品状态,坏处是"一个文件不满意"只能全盘否决再重来。如果你想让 AI 先搞定核心逻辑再处理测试文件,分两次指令比一次全说完效果好。

4. 沙箱不是 Docker

Codex 的沙箱是 OS 级强制访问控制,不是容器。macOS 上用的是 Seatbelt Sandbox,Linux 上用的是 bubblewrap(bwrap)+ seccomp 系统调用过滤。文件修改在你的实际目录中进行,但受内核策略约束——不是你想象的那种"在隔离环境里跑了个 Docker 容器"。


跟 Claude Code 比,什么时候用 Codex

读完上一篇又看完这篇,你可能会问:到底什么时候用 Codex,什么时候用 Claude Code。

从零建新项目,我用 Codex。沙箱里搭骨架、装依赖、跑测试,全程隔离,最后确认了一键落地。

改老项目,用 Claude Code。它能读到已有代码的完整上下文,边改边生效,不用最后才同步。

不确定 AI 能不能搞定的时候,先用 Codex 试试。沙箱里随便折腾,不行就否决,零成本。


下一篇

安装说到底一条命令就完了,但真遇到问题——代理配不上、Node 版本冲突、权限报错——才是折磨的开始。下一篇专门处理安装排障,把我和身边朋友踩过的坑一次说清楚。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐