第一个 Codex 任务:关在沙箱里,让它从零建一个 API
摘要: 实验使用OpenAI Codex从零构建Flask Todo API,全程在沙箱中自动完成。通过简单指令,Codex在3分钟内生成完整项目结构,包括: 创建app.py(含SQLAlchemy模型和CRUD路由) 编写test_app.py(5个pytest用例全部通过) 生成requirements.txt依赖文件 自动安装依赖并验证API功能 最终一键落地生产级代码,无需手动编写。测试
第一个 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 版本冲突、权限报错——才是折磨的开始。下一篇专门处理安装排障,把我和身边朋友踩过的坑一次说清楚。
更多推荐



所有评论(0)