
1. 从一条 Issue 到 PR无人值守仓库维护到底卡在哪先说清楚这套东西是什么用 GitHub Webhook 监听 Issue 事件触发本地常驻的 Codex CLI 自动读 Issue、改代码、跑测试、提交分支并创建 PR人只负责最后 review 合并。它适合谁适合个人开发者维护自己的开源仓库或小团队内部仓库Issue 大多是 bug 修复、小功能补齐、文档更新这类边界清晰的任务。不适合谁不适合把生产库直接交给它也不适合指望它独立完成大型功能开发。我折腾这套链路的起点很朴素每次看到 Issue 里写着「这个按钮点不动」「文档里链接挂了」手动切分支、改代码、跑测试、写 commit、开 PR一套下来十几分钟没了。如果一天来五个这种小 Issue半天就搭进去了。GitHub Copilot 有类似能力但价格对个人项目不友好于是决定自己拼一条链路出来。链路的核心难点其实不在「让 Codex 改代码」这一步Codex CLI 本身已经能读文件、执行命令、写代码。真正的坑集中在三个地方第一Webhook 怎么安全地触发本地服务secret 校验不能省第二Codex 在无人值守模式下怎么授权 git push、gh pr create 这类需要提权的命令TUI 里点「同意」的交互在 CLI 下必须换成规则文件第三PR 收到 review comment 后怎么让 Codex 接着上一轮对话继续改而不是从零重新理解上下文。这三个问题解决了整条链路就通了。下面按「前置准备 → Webhook 监听 → Codex 调用配置 → 端到端验证 → 报错排查」的顺序拆开讲每一步都给可复制的片段。2. TaoToken 前置把 Codex CLI 的模型调用接稳Codex CLI 要跑起来得先解决模型调用的问题。我这边用的是 TaoToken 的接口它兼容 OpenAI 风格的调用方式配置起来比较直接。官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。先说清楚为什么这一步要单独拎出来讲。无人值守场景下模型调用必须稳定因为没人盯着一旦 Key 失效或者 Base URL 配错Webhook 触发了但 Codex 起不来你只会看到日志里一堆报错。所以前置配置要做扎实。Codex CLI 的配置分两块一块是模型提供方的 Base URL 和 Key一块是 Codex 自己的运行参数。前者通过环境变量或配置文件注入后者通过命令行参数控制。先拿 Key。登录 TaoToken 控制台在 API Keys 页面创建一个新 Key复制出来。这个 Key 只显示一次丢了就重新建。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。拿到 Key 之后配置 Codex CLI 的模型接入。Codex 支持通过~/.codex/config.toml或者环境变量指定模型提供方。我这边用的是环境变量方式因为无人值守服务通过 systemd 加载.env文件环境变量注入最省事。在.env里加上这几行# TaoToken 模型接入配置 OPENAI_BASE_URLhttps://taotoken.net/api OPENAI_API_KEYsk-你的TaoToken密钥注意OPENAI_BASE_URL后面不要带/v1Codex CLI 会自己拼接路径。如果你用的是其他兼容 OpenAI 协议的工具有些需要带/v1这里以 Codex CLI 为准。配好之后先手动验证一次模型调用能不能通。在终端里执行codex exec --cd /path/to/your/repo --sandbox workspace-write --json - EOF 读取当前目录下的 README.md用一句话总结这个项目是做什么的。 EOF如果配置正确你会看到 Codex 输出一段 JSON 流里面有thread.started、item.completed这类事件最后有一条agent_message包含总结内容。如果报 401说明 Key 不对如果报连接超时检查 Base URL 是否写错。这一步验证通过之后再往下搭 Webhook 监听。顺序不能反因为 Webhook 触发后调用的就是这条 Codex 命令模型调用不通后面全白搭。关于模型选择Codex CLI 默认会用一个通用模型你也可以在配置里指定。对于自动修 Issue 这种任务我建议用推理能力较强的模型因为要理解 Issue 描述、定位代码、生成修复方案。具体模型 ID 可以在 TaoToken 的模型对话页面查看地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。如果你打算长期跑这套自动化建议用 Coding Plan额度更划算适合这种持续消耗的场景。入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。3. 可复制配置Webhook 监听器 Codex 调用参数 PR 创建脚本这一节是整篇的核心给的都是能直接复制粘贴的片段。我按「监听器配置 → Codex 调用参数 → 授权规则 → PR 创建」的顺序来。3.1 Webhook 监听器配置监听器是一个 Python 脚本用标准库的http.server起一个 HTTP 服务接收 GitHub Webhook 推送。核心逻辑是校验签名 → 解析事件 → 判断是否该处理 → 调起 Codex。先看.env配置这是监听器读取的所有参数# Webhook 密钥必须和 GitHub 仓库 Webhook 里填的一致 AUTOFIX_WEBHOOK_SECRET换成一个足够长的随机字符串 # 仓库路径 AUTOFIX_REPO_DIR/path/to/your/repo # 监听地址和端口 AUTOFIX_HOST127.0.0.1 AUTOFIX_PORT8765 # GitHub 仓库全名 AUTOFIX_GITHUB_REPOyour-name/your-repo # 触发标签只有带这个标签的 Issue 才会自动处理 AUTOFIX_LABELautofix # Codex 可执行文件路径 AUTOFIX_CODEX_BINcodex # Codex 沙箱模式 AUTOFIX_CODEX_SANDBOXworkspace-write # Codex 授权模式on-request 表示按需提权 AUTOFIX_CODEX_APPROVALon-request # Codex 单次任务超时时间单位秒 AUTOFIX_CODEX_TIMEOUT7200 # Git 提交者信息 AUTOFIX_GIT_AUTHOR_NAMEAutofix Bot AUTOFIX_GIT_AUTHOR_EMAILautofixexample.com # 日志目录 AUTOFIX_LOG_DIRlogs AUTOFIX_SESSION_CSVlogs/autofix-sessions.csv监听器的签名校验逻辑这是安全的关键不能省import hashlib import hmac import secrets def verify_github_signature(handler, body: bytes) - bool: provided handler.headers.get(X-Hub-Signature-256, ) if not provided.startswith(sha256): return False expected_digest hmac.new( required_secret().encode(utf-8), body, hashlib.sha256 ).hexdigest() return secrets.compare_digest(provided, fsha256{expected_digest})secrets.compare_digest是防时序攻击的别用比较。3.2 Codex 调用参数Codex CLI 在无人值守模式下关键是两个参数-a控制授权模式--sandbox控制沙箱权限。codex \ -c approvals_reviewerauto_review \ -a on-request \ exec \ --cd /path/to/your/repo \ --sandbox workspace-write \ --json \ -逐个解释-c approvals_reviewerauto_review指定审批审查器为自动模式这样提权请求会对照规则文件自动判断而不是等人点确认。-a on-request是授权模式。Codex 的授权模式有几个选项never会拒绝所有提权untrusted只允许安全命令on-request允许模型按需请求提权。无人值守场景必须用on-request因为git push、gh pr create这些命令需要提权。--sandbox workspace-write给工作区写权限但.git目录默认是只读的所以 git 写操作需要额外提权。--json输出 JSONL 格式日志方便后续解析 session id。最后的-表示从标准输入读取提示词。3.3 授权规则文件这是解决「CLI 下怎么自动授权」的关键。在仓库的.codex/execpolicy/autofix.rules里写规则prefix_rule(pattern[git, fetch], decisionallow) prefix_rule(pattern[git, pull], decisionallow) prefix_rule(pattern[git, push], decisionallow) prefix_rule(pattern[git, commit], decisionallow) prefix_rule(pattern[git, status], decisionallow) prefix_rule(pattern[git, log], decisionallow) prefix_rule(pattern[git, merge], decisionallow) prefix_rule(pattern[git, stash], decisionallow) prefix_rule(pattern[git, checkout], decisionallow) prefix_rule(pattern[git, diff], decisionallow) prefix_rule(pattern[git, add], decisionallow) prefix_rule(pattern[gh, repo], decisionallow) prefix_rule(pattern[gh, issue], decisionallow) prefix_rule(pattern[gh, pr], decisionallow) prefix_rule(pattern[gh, run], decisionallow) prefix_rule(pattern[gh, workflow], decisionallow) prefix_rule(pattern[gh, search], decisionallow) prefix_rule(pattern[gh, api], decisionallow) prefix_rule(pattern[npm, ci], decisionallow) prefix_rule(pattern[npm, install], decisionallow) prefix_rule(pattern[npm, run], decisionallow) prefix_rule(pattern[npm, test], decisionallow) prefix_rule(pattern[docker, compose], decisionallow) prefix_rule(pattern[docker-compose], decisionallow)这里有个坑我踩过Codex 执行时看不到这个规则文件的内容所以它请求提权的命令形式可能和规则里写的不完全一致。解决办法是在提示词里明确告诉 Codex 哪些命令族是被允许的让它用直接命令形式别用 shell 包装。3.4 PR 创建脚本Codex 完成代码修改后需要提交、推送、创建 PR。这部分逻辑写在提示词里让 Codex 自己执行。提示词的关键片段## Branch Workflow 1. Start from master. 2. Pull the latest master before creating the work branch. 3. Create one branch for the issue. 4. Branch naming: - Bug issues use fix/issue-number-slug. - Feature issues use feat/issue-number-slug. ## Commit And Pull Request 1. Commit with a Conventional Commits message in English. 2. The commit message must close the issue, for example Closes #38. 3. Push the branch. 4. Create a pull request with gh pr create. 5. Target master. 6. Do not merge the PR. Human reviewers merge.对应的 git 和 gh 命令Codex 会自己拼git checkout -b fix/38-button-not-clickable git add . git commit -m fix: resolve button click issue Closes #38 git push origin fix/38-button-not-clickable gh pr create --title fix: resolve button click issue --body Closes #38注意git add .之前提示词里要求 Codex 先跑git status --short --untracked-filesall检查避免把.env、logs/这些不该提交的文件加进去。4. 验证请求从 Issue 到 PR 的端到端跑通配置写完得实际验证一遍。我按「本地手动触发 → GitHub Webhook 触发 → PR review 续跑」三个层次来验证。4.1 本地手动触发先不接 GitHub直接在本地构造一个带签名的请求验证监听器和 Codex 调用链路。启动监听器python3 listener.py --host 127.0.0.1 --port 8765健康检查curl http://127.0.0.1:8765/health返回{ok: true, active_job: null}说明服务起来了。然后构造一个模拟 Issue 事件的请求。签名计算payload{action:opened,issue_number:38,repository_full_name:your-name/your-repo} signature$(AUTOFIX_WEBHOOK_SECRET$AUTOFIX_WEBHOOK_SECRET PAYLOAD$payload python3 - PY import hashlib import hmac import os secret os.environ[AUTOFIX_WEBHOOK_SECRET].encode() payload os.environ[PAYLOAD].encode() print(sha256 hmac.new(secret, payload, hashlib.sha256).hexdigest()) PY ) curl -X POST http://127.0.0.1:8765/autofix/github/issues \ -H X-Hub-Signature-256: $signature \ -H Content-Type: application/json \ -d $payload如果返回{accepted: true, job: {...}}说明监听器接受了请求并启动了 Codex 任务。4.2 观察 Codex 执行日志Codex 的日志是 JSONL 格式直接tail -f看着头大。我写了个格式化脚本tail_logs.py把 JSON 事件转成可读文本python3 tail_logs.py logs/20260609T070524Z-issue-38-1b2c662a6f30.log输出大概长这样[thread] 0196a1b2-c3d4-7e8f-9a0b-1c2d3e4f5a6b [turn] started [agent] 我先读取 AGENT.md 了解项目规范... [cmd:completed] git status --short --untracked-filesall (exit 0) [cmd:completed] gh issue view 38 (exit 0) [agent] 问题定位在 frontend/src/components/Button.tsx... [files:completed] edit:frontend/src/components/Button.tsx [cmd:completed] npm run build (exit 0) [cmd:completed] git checkout -b fix/38-button-not-clickable (exit 0) [cmd:completed] git commit -m fix: ... (exit 0) [cmd:completed] git push origin fix/38-button-not-clickable (exit 0) [cmd:completed] gh pr create ... (exit 0) [turn] completed看到最后gh pr create成功去 GitHub 仓库的 PR 列表里就能看到新 PR。4.3 GitHub Webhook 真实触发本地验证通过后配置 GitHub 仓库的 Webhook。进入仓库 Settings → Webhooks → Add webhookPayload URL 填你的公网地址加/autofix/github/issues比如https://your-domain.com/autofix/github/issues。Content type 选application/json。Secret 填和.env里AUTOFIX_WEBHOOK_SECRET一样的值。事件选择Let me select individual events勾选Issues、Issue comments、Pull request reviews、Pull request review comments。保存后GitHub 会发一个ping事件监听器返回pong不启动 Codex。然后去仓库新建一个 Issue内容写清楚要修什么。如果配置了AUTOFIX_LABEL给 Issue 打上autofix标签。等几秒监听器日志里应该能看到新任务启动。4.4 PR review 续跑验证PR 创建后在 PR 下留一条 review comment比如「这个边界条件没处理麻烦补一下」。监听器收到pull_request_review_comment事件从 CSV 里找到对应的 session id用codex resume继续对话codex -c approvals_reviewerauto_review -a on-request resume session_id PR 收到新反馈请处理...Codex 会读取 PR 的最新评论在原有上下文基础上继续修改然后 push 到同一个分支PR 自动更新。session id 和 Issue/PR 的对应关系存在logs/autofix-sessions.csv里issue_number,pr_number,repository,session_id,resume_command,job_id,log_path,metadata_path,updated_at 38,42,your-name/your-repo,0196a1b2-c3d4-7e8f-9a0b-1c2d3e4f5a6b,codex resume 0196a1b2-c3d4-7e8f-9a0b-1c2d3e4f5a6b,1b2c662a6f30,logs/xxx.log,logs/xxx.json,2026-06-09T07:05:24Z5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节列的都是我实际遇到过的报错按报错信息对照排查。5.1 401 invalid webhook signature监听器返回{error: invalid webhook signature}原因通常是三个.env里的AUTOFIX_WEBHOOK_SECRET和 GitHub Webhook 里填的 Secret 不一致请求体被中间层改过导致签名对不上或者 Content-Type 不是application/json监听器解析方式不对。排查步骤先确认两边 Secret 完全一致注意有没有多余空格。然后用本地手动触发的方式验证签名计算逻辑如果本地能过、GitHub 触发不过检查反向代理有没有修改请求体。5.2 local proxy failed / connection refusedCodex 日志里出现error: local proxy failed to connect或者connection refused这是模型调用层的问题。检查OPENAI_BASE_URL是否写对TaoToken 的地址是https://taotoken.net/api不要多加路径。检查OPENAI_API_KEY是否有效可以去控制台重新生成一个。还有一种情况是监听器启动 Codex 时把代理环境变量清掉了但你的网络环境又需要代理才能访问模型接口。监听器的codex_environment()函数会移除HTTPS_PROXY等变量如果你确实需要代理访问模型得改这个逻辑或者用不需要代理的网络环境。5.3 reading choices / unexpected response formatCodex 日志里出现error reading choices from response或者unexpected response format这通常是 Base URL 配错导致的。有些兼容接口需要/v1后缀有些不需要。Codex CLI 期望的是标准 OpenAI 格式的/chat/completions端点。如果 Base URL 写成https://taotoken.net/api/v1Codex 会拼成https://taotoken.net/api/v1/chat/completions可能不对。正确写法是https://taotoken.net/apiCodex 自己拼/v1/chat/completions。如果还是报错用 curl 直接测一下接口curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $OPENAI_API_KEY \ -H Content-Type: application/json \ -d {model:gpt-4o,messages:[{role:user,content:hi}]}能返回正常 JSON 说明接口没问题问题在 Codex 配置。5.4 OAuth token expired / authentication failedCodex CLI 本身可能需要登录认证如果你用的是需要 OAuth 的模式。日志里出现OAuth token expired或者authentication failed检查 Codex CLI 的登录状态。如果是 API Key 模式确认OPENAI_API_KEY环境变量在 systemd 服务里能读到。systemd 的EnvironmentFile指向.env确认路径正确、文件权限可读。还有一种情况是ghCLI 的认证过期。gh pr create需要 GitHub 认证检查gh auth status如果显示未登录重新执行gh auth login。5.5 git push 被拒绝 / .git/index.lock 问题日志里出现could not create .git/index.lock或者Permission denied这是因为.git目录在沙箱里是只读的git 写操作必须提权。检查.codex/execpolicy/autofix.rules里有没有对应的git push、git commit规则。另外提示词里要明确告诉 Codexgit 写操作必须用提权模式执行不要先试普通沙箱。如果规则文件里有但还被拒检查 Codex 请求提权的命令形式是否和规则里的pattern完全匹配。比如规则里写的是[git, push]但 Codex 实际执行的是/bin/bash -lc git push前缀匹配不上。解决办法是在提示词里要求 Codex 用直接命令形式。5.6 任务卡住不结束监听器返回409 another job is active说明上一个任务还没结束。检查logs/下最新日志看 Codex 卡在哪一步。常见原因是某个命令等待输入或者网络请求超时。AUTOFIX_CODEX_TIMEOUT控制单次任务超时默认 7200 秒。如果任务经常超时可以调小这个值让失败的任务快速释放锁。6. 把链路用起来从手动 review 到长期自动化整套链路跑通之后日常使用其实很简单新建 Issue打上autofix标签等 PR 出现review 合并。PR 有问题就在 PR 下留 commentCodex 会自动续跑修改。但有几个实践建议是我踩坑之后总结的。第一Issue 描述要写清楚。Codex 不是人它靠 Issue 文本理解需求。如果 Issue 只写「按钮有问题」它可能改错地方。建议在 Issue 里写清楚复现步骤、期望行为、实际行为、相关文件路径。这些信息越全Codex 一次改对的概率越高。第二提示词要持续迭代。我一开始的提示词很简陋Codex 经常忘记跑测试、忘记写Closes #38。后来把「完成标准」写进提示词明确列出必须跑构建、必须写 Conventional Commit、必须创建 PR、必须写总结文档。这些约束加上之后输出质量稳定很多。第三别让它碰生产库。这套链路适合个人仓库、内部工具仓库、文档仓库。涉及生产环境配置、数据库迁移、支付逻辑的仓库不要让自动化直接提 PR 合并人工 review 必须卡住。第四多任务和重试机制可以后续加。当前实现一次只跑一个任务用git worktree加多线程可以支持并发。网络抖动导致的任务失败可以加重试逻辑。通知方面接一个飞书或钉钉的 Webhook任务完成或失败时推消息比盯日志省事。第五session 管理要留好。logs/autofix-sessions.csv记录了 Issue、PR、session id 的对应关系。想手动接管某个任务时从 CSV 里找到 session id执行codex resume session_id就能进入 TUI 继续开发。这比在 GitHub 上一条条评论效率高。如果你打算把这套链路长期跑起来模型调用这块建议用 Coding Plan额度稳定适合这种持续消耗的场景。入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。API Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。最后说一个实际感受这套东西不是「设置完就不用管」的银弹。它更像一个勤快但需要指导的实习生简单任务能独立完成复杂任务需要你拆细、给足上下文。但就是这种「简单任务自动化」把每天重复的十几分钟省下来了长期看很值。