【Vibe Coding】折腾了一个高考假,我让Codex自

发布时间:2026/6/28 20:51:08
【Vibe Coding】折腾了一个高考假,我让Codex自 前言随着Codex等AI Agent的发展Vibe coding逐渐成为了开发者的日常。前几天修issue的时候发现所有的活基本上都让codex干了只需要一句“修一下Issue #34”codex便自己调用gh-cli查看issue列出plan查找作用域甚至修完代码还帮你测试。我就想着或许我们可以连输这句话都不用直接在github上提交issue本地的codex自动修完提pr人类只需要review...其实Github Copilot有类似的功能可惜太贵于是开始自己折腾起这条工作流来。Pre-requirement一台常开机的本地开发环境gitpython运行时公网访问或使用内网穿透可用的codex cli已经登录授权的gh cli工作流依赖Githubgitee等平台是否可行有待试验。Webhook监听既然要自动修Issue那肯定是要监听Issue提交的。这里有三种方案使用gh-cli轮询使用Github Workflow事件触发使用Github Webhook第一种方案显然效率低下而且说不定会达到api request limit被封一开始实践的是第二种方案使用workflow监听issue事件然后run curl通知不过显然第三种方案更佳。这里使用官方的仓库Webhook通知在仓库的设置里面要监听肯定是需要本地监听器的。这里我写了一个listener.py见文末开放了一个http服务监听webhook事件然后使用frp穿透出去。需要注意的是webhook需要一个secret值防止恶意请求。这里参考github的文档处理secret。在仓库设置里新建一个webhook填上穿透后的访问接口和一样的secret值然后事件自定义勾选Issues,Issue comments,Pull request reviews, 和Pull request review comments。处理事件接收事件之后便要调起codex。为了避免进入tui我们使用codex exec --json模式调用并把日志重定向到logs/文件夹下。满心期待的试验发现了一些问题第一调起来了codex也不知道你要干嘛第二有些指令沙箱内运行不了比如git push需要提权。提示词优化我写了一篇提示词告诉Codex了解项目上下文阅读Issue内容修复它允许提权的指令有哪些当然这里只是简约的列出要素实际上提示词还包括了代理使用、文档整理、完成标准、提交规范、工作流程、分支结构、测试方法等。完整文档见文末。然后在listener.py中将提示词文本注入到指令中并加上webhook得到的issue相关信息。指令审核在我们使用CodeX TUI的时候经常需要授权同意执行指令。那么在CLI下怎么授权呢我阅读了codex文档看见了-a参数以下表格由千问总结那么never显然是不行的会拒绝提权git和gh调用不了。而untrusted中不包含git、gh指令。所以选择on-request并且写了一个autofix.rules文件见文末作为approval-reviewer代替人类授权允许执行特定的指令。这里有点小插曲codex执行时是看不到这个rules文件的会导致它提权的命令和允许的规则有些许偏差。所以提示词里面需要告诉codex哪些指令可执行。继续对话用过codex的小伙伴都知道并不是第一次写出来的代码就完全满意总需要修改。所以充分利用github pr review的comment功能提交comment之后触发事件codex会继续修复。那么问题来了listener怎么恢复之前的对话呢这里我采用指定对话id的方式并且把对话id和issue编号、pr编号对应存到logs/下的csv文件里。这样就可以调用codex resume的方式继续修复。当然开发人员进入这个开发环境也可以使用codex resume seesion_id进入tui进行开发毕竟在github上对话效率不高。不过直接使用codex resume的列表里貌似没有这些cli对话需要从csv或者日志里找到对话id指定对话id才能resume不知道后续版本会不会把这些对话也加进去。总结折腾了几天的脚本和提示词最后基本上实现了自动修Issue的功能。这里我贴张图片就是codex自己修的pr测试后是完全达标可以直接合并的。这里提交用的环境里面指定的邮箱和名称提pr需要github账户用的gh-cli登录的用户。目前来看简单任务完成没有问题大的比如功能开发还是需要再手动改不过也正常啦。目前还有几个缺点可以优化比如不支持同时执行多个任务稳定性略差碰到网络问题等容易失效且没有反馈可以用git worktree和多线程实现多任务还有重试机制和部署飞书webhook通知等。不过目前够用就行啦。源码我把它从项目仓库里分离了出来放在单独的Github公开仓RunlingDev/autofix下不过有一些内容没有改要使用需要适配一下。比如建议把整个文件夹放在项目的chore/autofix下如果更改名字或层级可能需要扫一遍脚本里面关于路径处理的地方。有写了一个部署指南DEPLOYMENT.md可以参考。一般来说配置好Github Workflow和环境变量写.env有.env.example然后执行chore/autofix/install.sh即可。这里贴一下当前版本的代码可以直接用GitHub Repo上的也欢迎大家提PR优化毕竟目前也只是能用的水平~对了源码里面有一些HayFrp Verify之类的字样可能需要改改那是我原来部署的仓库github上的应该就不会有了我会整理完再传上去。install.sh:注使用systemd管理服务需要root运行。重复执行更新会覆盖原有配置。#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR$(CDPATH cd -- $(dirname -- $0) pwd) REPO_DIR$(CDPATH cd -- ${SCRIPT_DIR}/../.. pwd) ENV_FILE${SCRIPT_DIR}/.env SERVICE_NAMEhayfrp-verify-autofix.service SERVICE_PATH/etc/systemd/system/${SERVICE_NAME} require_root() { if [ $(id -u) -ne 0 ]; then echo install.sh must be run as root because it writes systemd service files. 2 exit 1 fi } require_command() { if ! command -v $1 /dev/null 21; then echo Missing prerequisite command: $1 2 exit 1 fi } require_docker_compose() { if command -v docker /dev/null 21 docker compose version /dev/null 21; then return 0 fi if command -v docker-compose /dev/null 21; then return 0 fi echo Missing prerequisite command: docker compose or docker-compose 2 exit 1 } load_env() { if [ ! -f $ENV_FILE ]; then echo Missing env file: $ENV_FILE 2 echo Copy ${SCRIPT_DIR}/.env.example to ${SCRIPT_DIR}/.env and fill AUTOFIX_WEBHOOK_SECRET first. 2 exit 1 fi set -a # shellcheck disableSC1090 . $ENV_FILE set a if [ -z ${AUTOFIX_WEBHOOK_SECRET:-} ]; then echo AUTOFIX_WEBHOOK_SECRET is required in $ENV_FILE 2 exit 1 fi } write_codex_rules() { local rules_dir${REPO_DIR}/.codex/execpolicy local rules_file${rules_dir}/autofix.rules mkdir -p $rules_dir cat $rules_file EOF 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) EOF if [ -n ${HTTPS_PROXY:-} ]; then cat $rules_file EOF prefix_rule(pattern[/bin/bash, -lc, HTTPS_PROXY\${HTTPS_PROXY}\ git fetch], decisionallow) prefix_rule(pattern[/bin/bash, -lc, HTTPS_PROXY\${HTTPS_PROXY}\ git pull], decisionallow) prefix_rule(pattern[/bin/bash, -lc, HTTPS_PROXY\${HTTPS_PROXY}\ git push], decisionallow) prefix_rule(pattern[/bin/bash, -lc, HTTPS_PROXY\${HTTPS_PROXY}\ gh], decisionallow) prefix_rule(pattern[/bin/bash, -lc, HTTPS_PROXY${HTTPS_PROXY} git fetch], decisionallow) prefix_rule(pattern[/bin/bash, -lc, HTTPS_PROXY${HTTPS_PROXY} git pull], decisionallow) prefix_rule(pattern[/bin/bash, -lc, HTTPS_PROXY${HTTPS_PROXY} git push], decisionallow) prefix_rule(pattern[/bin/bash, -lc, HTTPS_PROXY${HTTPS_PROXY} gh], decisionallow) EOF fi echo Wrote Codex rules: $rules_file } write_systemd_service() { local python_bin python_bin$(command -v python3) cat $SERVICE_PATH EOF [Unit] DescriptionHayFrp Verify Autofix Listen Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple Userroot WorkingDirectory${REPO_DIR} EnvironmentFile${ENV_FILE} ExecStart${python_bin} ${SCRIPT_DIR}/listener.py Restartalways RestartSec5 [Install] WantedBymulti-user.target EOF echo Wrote systemd service: $SERVICE_PATH } wait_for_listener() { local host127.0.0.1 local port${AUTOFIX_PORT:-8765} local urlhttp://${host}:${port}/health local max_wait${AUTOFIX_INSTALL_WAIT_SECONDS:-30} local waited0 echo Waiting for autofix listener at $url ... while [ $waited -lt $max_wait ]; do if curl -fsS --max-time 2 $url /dev/null 21; then echo Autofix listener is ready. return 0 fi sleep 1 waited$((waited 1)) done echo Autofix listener did not become ready within ${max_wait}s. 2 systemctl status $SERVICE_NAME --no-pager 2 || true exit 1 } verify_endpoint_without_token() { local host127.0.0.1 local port${AUTOFIX_PORT:-8765} local urlhttp://${host}:${port}/autofix/github/issues local body_file body_file$(mktemp) local status_code set e status_code$(curl -sS -o $body_file -w %{http_code} -X POST $url \ -H Content-Type: application/json \ -d {action:opened,issue_number:0,repository_full_name:HayFrp-Team/Verify}) local curl_code$? set -e if [ $curl_code -ne 0 ]; then echo curl verification failed to connect to $url 2 cat $body_file 2 || true rm -f $body_file exit 1 fi if [ $status_code ! 401 ]; then echo Expected unauthenticated webhook check to return 401, got $status_code 2 cat $body_file 2 || true rm -f $body_file exit 1 fi rm -f $body_file echo Unauthenticated curl check returned 401 as expected. } main() { require_root require_command python3 require_command codex require_command gh require_command git require_command curl require_command systemctl require_docker_compose load_env write_codex_rules write_systemd_service systemctl daemon-reload systemctl enable $SERVICE_NAME systemctl restart $SERVICE_NAME wait_for_listener verify_endpoint_without_token echo Autofix listener installed and running. } main $listener.py:注基本上使用stdlib应该不需要另外装依赖#!/usr/bin/env python3 Webhook listener that starts one Codex autofix session per GitHub issue event. from __future__ import annotations import argparse import csv import datetime as dt import hashlib import hmac import json import os import re import secrets import subprocess import threading import uuid from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from typing import Any from urllib.parse import parse_qs BASE_DIR Path(__file__).resolve().parent REPO_DIR BASE_DIR.parents[1] DEFAULT_ENV_FILE BASE_DIR / .env DEFAULT_PROMPT BASE_DIR / codex-prompt.md DEFAULT_LOG_DIR BASE_DIR / logs DEFAULT_SESSION_CSV DEFAULT_LOG_DIR / autofix-sessions.csv JOB_LOCK threading.Lock() ACTIVE_JOB: dict[str, Any] | None None