CI/CD 怎么用:AI 改完的代码,凭什么判断它能上线
流水线不是「自动发布」,是每次改动都自动过一遍同样的检查,通过了才允许发布。发布只是最后一步,前面那些检查才是它真正值钱的地方——因为在 AI 编程里,它是你唯一一个不用看懂代码就能验收 AI 的东西。
📅 更新于 2026-09-23
0030 秒速览:三句话,和你的下一个动作
读完这篇你能做到两件事:① 打开 AI 给你的那份流水线配置,两分钟内说出「它什么时候跑、跑哪几步、哪一步失败会挡住合并」;② 知道在 AI 编程里拿它当验收机器用,而不是当省事工具用。
如果只记三句话
① 它不是「自动发布」,是「每次改动都自动过一遍同样的检查,通过了才允许发布」。发布只是最后一步。前面那些检查才是它值钱的地方——因为只有它们能回答「这次改动能不能出去」。把 CI/CD 理解成部署工具,你会漏掉它八成的价值。
② 它不在你的电脑上跑,也不在你的服务器上跑,而是在平台临时给你的一台干净机器上跑。这一条解释了你以后会遇到的一半怪事:为什么「我本地跑得好好的」不算证据、为什么每次都要重装依赖、为什么不能把密码放在那儿。
③ 在 AI 编程里,它最大的用途是给你一个「不用看懂代码也能验收」的裁判。AI 可以说「我改好了」,但它没法把红的说成绿的——结论由平台记在一台你控制不了的机器上。你要付出的代价很小:读懂那份配置,并且亲手把「必须通过」这个开关打开。
接下来 60 秒:你的第一个动作
打开你的一个 GitHub 仓库,在网页上新建文件 .github/workflows/hello.yml,粘贴下面这九行(一行都不用改),提交:
name: 我的第一条流水线
on:
push:
jobs:
hello:
runs-on: ubuntu-latest
steps:
- run: echo "流水线跑起来了"
然后点仓库顶部的「Actions」,看它从黄点变成绿勾。这一步值的不是那条命令行,是你亲眼看到的那个状态变化。完整的走查(包括怎么故意弄红它、看红灯长什么样)在 §05。
① 一个已经推到 GitHub 的仓库。没有的话,这篇里的每一步你都无处可做——先去建一个,本仓的 Git 和 GitHub 快速入门 是从零讲起的。
② 一个你正在用 AI 改的项目(哪怕只有几个文件)。不需要它已经上线,也不需要它有测试——§09 会告诉你不同起点各该上哪一层。
关键数字(先有个量级感)
每月含额度(免费档为 2,000 分钟),超额按分钟计费
不确定自己该看哪一节?
| 你的情况 | 直接跳 |
|---|---|
| 完全没概念,想知道它到底在讲什么 | §01 CI 和 CD 是两个东西 → §02 跑在谁的机器上 |
| 知道个大概,但一看 YAML 就懵 | §03 一次运行的全过程 → §04 逐词拆 YAML |
| 想直接动手,做完再说 | §05 最小例子(全程网页操作,不用装东西) |
| 我只想知道在 AI 编程里怎么用 | §08;其中安全那部分看 真实事故 |
| 我在犹豫要不要上,怕白折腾 | §09 六类场景 → §10 什么时候不该做 |
| 已经配了,但红了不知道怎么查 | §12 红灯先看这五处 |
01CI 和 CD 是两个东西,而 CD 还是两个意思
你大概率已经摸到过它的一半了:某个项目你把它连上了 Vercel(或 Cloudflare Pages、Netlify),之后你在本地改完代码一推送,过一分钟线上就变了。那条链子里,平台其实替你做了三件事——拉取新代码、重新构建、把结果发布出去。CI/CD 就是把这条链子写成明文清单、并且让它在每次改动时都自动跑的东西。
所以先别背三个字母。先把「它不是什么」划清楚,因为绝大多数人第一次听到 CI/CD 时,脑子里装的是别的东西。
它不是什么
它不生成代码、不修 bug。它只做一件事:执行你已经写好的检查命令,然后如实报告成功或失败。你把「不许有语法错误」写进去,它就每次帮你验一遍;你不写,它不会替你想起来。
部署(把新版本放到线上让用户用)只是流水线上最后一步。整条流水线上通常有五六步,部署排在最后;前面几步都是「先验一遍,别把坏的放上去」。把 CI/CD 理解成部署工具,你会漏掉它 80% 的价值。
测试是你写的一段会自己检查对错的程序。CI 是让这段程序每次改动都必然被跑一遍的那个机制。没有 CI,测试写在那儿也会被你忘掉;有了 CI,忘不掉。
你在自己电脑上装个「保存时自动格式化」的插件,那叫编辑器插件。流水线跑在别人的机器上(下一节讲),这件事决定了它为什么能当证据——你自己电脑上跑出来的结果,别人复现不了。
它是什么:一句话版本
把「每次改动都必须做的事」写成一份机器能照着执行的清单,交给平台在每次改动时自动执行,并把结果压成一个「通过 / 不通过」的结论。
这份清单里放什么,由你决定:可以只是「语法检查」,可以是「跑两百个测试」,也可以是「检查完就自动发到线上」。清单本身是明文的,谁都能打开看,改一行就改行为——这是它和「某个平台的黑盒自动部署」最大的区别。
「持续集成」这四个字到底在集成什么
CI 是 Continuous Integration 的缩写。翻译过来是「持续集成」,这四个字里最费解的是集成——它不是「集成开发环境」那个集成,而是软件行业的一个旧词,意思是把不同人(或不同时间)写出来的改动合成到同一份代码里。
持续的意思是:不要攒着。别等三个月发布一次,那时候把大家的改动合到一起。而是每改一点就合一次,一天合好几次。
为什么非要频繁合?因为分歧会随时间长大。假设你和另一个人(或者你和三天前的你自己)同时改了一个文件:改动放一天,冲突可能只是同一行;放三个月,两边都基于完全不同的底稿写了几百行,最后合并就变成一件需要重新做一遍的工作。软件行业把这种「两份改动合不拢」的情况叫做冲突,合的动作叫合并。
CI 的重点从来不是「自动跑测试」,而是逼你频繁合并。跑测试只是让频繁合并变得安全的手段:你要一天合十次,就不敢每次靠肉眼检查,只能让机器替你检查。顺序是「先频繁合,再自动验」——如果你并不频繁合(一个人开发、改完就推),那你从 CI 拿到的主要就是「自动验」这一半,这依然有用,但要知道自己用的是哪一半。
CD 有两个意思,而且两个意思差别很大
CD 这个缩写是有歧义的,官方文档和行业书里经常混用,这是新手最容易被绕晕的一处。它其实是两个不同的承诺:
| 写法 | 中文 | 它的准确含义 / 你该拿它判断什么 |
|---|---|---|
| Continuous Delivery | 持续交付 | 每次改动检查通过后,软件随时可以被发出去,但「发」这个动作由人按。判断依据:发布按钮还在你手上。 |
| Continuous Deployment | 持续部署 | 检查通过就自动发出去,没有人按按钮。判断依据:你睡觉时线上也可能变。 |
这两个词只差几个字母,风险却完全不同:持续交付错了,你还有一道人工闸门;持续部署错了,坏版本会自己走到用户面前。所以别人跟你说「我们上了 CD」时,值得追问一句:是能一键发,还是自动发?——这句话能立刻把对方的话分出层级。
那 CI/CD 里到底有几步
把缩写拆开,一条典型的流水线是这样的(每一步的名字你都可能在别人的 YAML 里见到):
这六步里,只有最后一步是「发布」。前面五步的存在意义是同一件事:让坏东西到不了最后一步。
易混的几组词,先用一张表分清
| 容易混的一对 | 差别在哪 | 你什么时候会真的踩到这个差别 |
|---|---|---|
| CI/CD ↔ 自动部署 | 自动部署只做最后那一步;CI/CD 是从「检查」到「发布」的一整条链 | 有人卖你一个「一键部署」的插件时说「这就是 CI/CD」——它只是那条链的尾巴 |
| CI ↔ 测试 | 测试是内容,CI 是「每次都必须跑」这个制度 | 你有测试但线上还是坏:因为没人每次跑它 |
| 持续交付 ↔ 持续部署 | 前者要人按,后者自己发 | 决定「AI 改完能不能自己上线」时,这就是那个开关 |
| 构建 ↔ 部署 | 构建是把源码加工成能运行的成品;部署是把成品放到用户能访问的地方 | 失败日志里写着 build failed 时,问题不在服务器,在加工那一步 |
| 绿色 ↔ 没问题 | 绿色只说明你写进清单的那些检查都过了 | 你把检查写得很松,它会一直绿着,而线上坏着(§11 误解第四条) |
没有它的时候,会发生什么
不装流水线,不代表你没有流程,只代表流程跑在你脑子里。手动发布会有三个几乎必然的后果:
「改完要记得把那个配置文件也改一下」——这句话的顺序、条件、依赖,全靠人记。人一忙就漏,漏了不会立刻报错。
你电脑上装了某个工具,发布用的机器上没装。同一份代码,两处结果不同,而且你无法解释差别在哪。
改到第四版时,你已经说不清线上跑的是哪次改动。出问题想退回上一版,得先猜「上一版」是什么。
而在 AI 编程的场景里,还有第四个后果,比上面三个都严重:AI 一天能改十次,你验不完。验不完的时候人只有两条路——要么停下来不验,要么开始相信 AI 说的「改好了」。你手上那十几个半途停下的项目,多半就停在两种感觉之间:「让它继续改,我越来越不知道它在干什么」和「我自己又判断不了对不对」。
流水线是这两难之间少见的出路:它不要求你看懂代码,只要求你把「什么样算过关」写成机器能执行的话。这件事你做得来,而且做完之后,判断权就回到你手上了。
02跑在谁的机器上:「我本地是好的」为什么不算数
这一节讲的是全篇最该先建立的一个事实。它只有一句话,但它能解释你以后遇到的一大半奇怪现象。
流水线不在你的电脑上跑,也不在你的服务器上跑。它跑在平台(比如 GitHub)临时给你的一台机器上:你推送代码,平台分配一台刚装好系统的空机器,把你的代码下载下来,按清单逐步执行,跑完就销毁。
那台机器长什么样
它是一个真实存在的计算机,只是你看不见它。它的特点是:干净——上一次运行的残留不会留下,你的桌面、你装过的软件、你电脑里的密码,它一样都没有。临时——跑完就扔,下次是新的一台。由平台统一维护——操作系统和常用工具是预装好的,但你自己项目依赖的那些库,得每次现装。
行业里管这台机器叫 runner(执行器)。平台提供的叫 托管 runner;你也可以把自己的一台服务器接进去当 runner(叫自托管 runner),但那样你就得自己负责它的安全、补丁和在线率——除非你的项目必须访问内网或特殊硬件,否则没必要。
这个事实解释了三种「怪事」
怪事一:我本地跑得好好的,为什么它说失败?
因为「你本地」和「那台机器」是两套不同的环境,你的电脑上有很多你没意识到的东西:装过的全局工具、缓存下来的依赖、设好的环境变量、特定版本的系统。流水线失败的原因里,很大一部分不是「代码写错了」,而是「那台机器上少了一件事」。
最常见的具体形态:你在本地装依赖时装了一堆东西,机器上重新装一遍时,某个包的版本和你的不同,于是行为不一样。这不是流水线的毛病,这是流水线替你发现的、你原本不知道的隐患——你的项目其实一直依赖着「你电脑的当前状态」,而这种依赖是无法交给别人的。
怪事二:为什么每次都要重新装依赖、重跑一遍?
因为机器是新的,什么都不记得。这正是它可信的原因(结果只取决于代码和清单,不取决于「上次留下的什么」),代价是每次都要从头来。为了不让「从头来」太慢,各平台都提供了缓存机制:把「装依赖」的结果存起来,下次直接取。这就是你在别人的 YAML 里看到的那些 cache 步骤存在的原因——它们不是装饰,是在买时间。
怪事三:为什么不干脆把数据库密码写进去?
因为那台机器是别人维护的、每次是新的、而且执行过程会留下日志。给它什么,它就能看到什么;日志里出现什么,能看到日志的人就能看到什么。第 07 节整节都在讲这件事,这是全篇唯一一处「不看会真的出事」的地方。
你会在 YAML 里怎么指定「要一台什么样的机器」
清单里有一行专门写这个,叫 runs-on(跑在什么上)。它的值是一串标签,比如:
# 要一台最新版 Ubuntu 系统的机器(最常用的选择)
runs-on: ubuntu-latest
# 也可以点名要 Windows 或 macOS(比如要给 iPhone 打包)
runs-on: windows-latest
runs-on: macos-latest
标签里的 latest 意思是「当前默认的那个版本」。这一点值得注意:它会在平台更新默认版本时悄悄变化,所以「今天能跑」不代表「三个月后还能跑」。这也是「可核对断言」里要盯的一类东西。
「那台机器免费吗」——钱的常识
GitHub 的托管机器对公开仓库是不限时长的;私有仓库每月有一份免费额度,用超了按分钟计费,不同套餐额度不同。这个额度会变,具体数字以官方计费页为准(第 15 节给了链接)。你要建立的是量级感:一条只跑几分钟检查的流水线,在一个人的项目里几乎不可能花到钱;真正吃时间的是「每次都在全新机器上重装一大堆依赖」这类没加缓存的配置。
平台的免费额度是按机器分钟算的,而不同系统的机器倍率不一样(例如 macOS 的机器通常按更高的倍率扣)。如果你只是想跑个语法检查和测试,用默认的 Ubuntu 机器是最省的——除非项目必须用别的系统。
03一次 push 之后,机器按什么顺序干活
这一节把「按清单执行」这句抽象的话,摊成一次真实的运行过程。看完你会有一个能立刻用上的能力:打开任何一个项目的流水线页面,你能看懂现在进行到哪一步了。
从你按下推送,到红灯或绿灯亮起
你在本地把改动提交(commit:把这次改动记进这个项目的历史)、推送(push:把这些记录上传到 GitHub)。平台上那一份代码更新了。
它翻出仓库里 .github/workflows/ 目录下的 YAML 文件,逐个问一个问题:这次发生的事,是不是我负责响应的?比如清单上写着「当有人推到 main 分支时」,而你推的正是 main——它就准备跑。(分支是同一个项目的平行副本,改坏了互不影响;main 是仓库默认的那一条,通常就是「正式的那一份」。)
平台分配一台干净的机器给你。人多的时候这里会等,界面上显示成「排队中」。等到了,机器开始工作。
这一步在几乎所有清单里都是第一行,叫「取代码」(checkout)。没有它,机器上什么都没有,后面的命令无处可跑。
项目要用的那些外部库,机器上一个都没有,得现装。这一步最慢,也最容易失败——不是因为代码错,是因为网络、版本或那个库本身出了问题。
检查语法格式、跑测试、构建成品……每条命令执行完都会返回一个「成功」或「失败」。任何一条失败,后面的步骤默认就不再执行了——所以日志里你经常看到后面几步是灰色的,那不是出错,是被跳过了。
比如构建好的成品、测试报告、截图。这些叫「产物」,可以下载,也可以交给后面的步骤用——例如「构建」这一步产出的文件,由「发布」这一步拿去放到线上。
成功还是失败,会记录在这一次运行里,并且和「具体是哪个提交」绑定。如果这次改动是一份合并请求(PR),结论还会显示在那份请求的页面上,成为你决定合不合的依据。
这一路上出现的九个词,各在哪一层
非程序员看流水线页面时最大的障碍是:一堆词都在,但不知道谁包含谁。下面这张表按「从大到小」排,看完你会知道每个词指的是页面上的哪一块。
| 词(YAML 里的写法) | 它是什么 / 在页面哪里 | 为什么重要 · 别当成什么 |
|---|---|---|
工作流 workflowname: |
一个 YAML 文件 = 一份清单。页面左侧那一列就是一个仓库里的所有工作流。 | 它是「一份」,不是一个「一条」。一个仓库可以有好几份清单,各管一类事(检查、发布、每周清理)。别以为 .yml 只有一个。 |
触发 triggeron: |
「什么时候跑」的条件。写在清单最上面。 | 这是整份清单里最该先看懂的一行。看不懂它,你就不知道「这次改动会不会被检查」。别把它当成注释跳过。 |
| 一次运行 run | 清单被执行一次,界面上有编号(#12、#13…)、有开始时间、有结果。 | 同一个清单会被跑很多次。出问题时先确认「你看的是哪一次运行」——这是最常发生的误会。 |
| 作业 job | 清单里的一组步骤,跑在单独一台机器上。页面左侧工作流下面那一层就是作业名。 | 作业之间默认互不相干、可以同时跑,所以两个作业之间不共享内存和文件。别以为写在同一个文件里就能互相拿到东西。 |
| 步骤 step | 作业里的一行:要么跑一条命令(run:),要么用一段现成的动作(uses:)。 |
每一步的日志是分开的。失败时只要看第一个红色的那一步,前面都是成功的。 |
现成动作 actionuses: |
别人写好、公开可复用的自动化小块,比如「取代码」「装 Node.js」「把文件传到页面服务」。 | 它带版本号。这是 AI 最常写错的地方之一(§04 会讲)。别把它当成你项目里的代码——它来自别人。 |
执行器 runnerruns-on: |
跑作业的那台机器(§02 讲过)。 | 默认是平台给的干净机器,跑完即销毁。别指望它记得上一次的东西。 |
| 产物 artifact | 运行过程中特意存下来的文件,可在页面上打包下载。 | 它也是「构建」和「发布」之间的传送带。别指望不声明就能自动传递。 |
| 状态检查 status check | 「这个提交通过了 / 没通过」这条记录,会显示在 PR 页面上。 | 它是「信息」;只有当你把它设成必须通过时,它才变成「否决权」(§06)。 |
作业是同时跑还是排队跑
默认同时跑:一份清单里写三个作业,平台就分配三台机器,三个一起开工。想让谁等谁,要显式写出来,用 needs(需要)这个词:
jobs:
# 作业名你随便起,这里叫 check
check:
runs-on: ubuntu-latest
steps:
- run: echo "先做检查"
# 这里写 needs: check,意思是「等 check 成功了再开始」
deploy:
needs: check
runs-on: ubuntu-latest
steps:
- run: echo "检查通过了,才轮到发布"
这就是「让坏东西到不了发布」在文件层面的样子:发布那个作业需要检查作业成功。你以后读配置,看到 needs 就该意识到这里有一道顺序上的关卡。
界面上你只需要看三处
第一次打开流水线页面,日志会长得吓人。实际上验收只需要看三处:
它列出这次运行包含的每个作业,谁成功、谁失败、谁被跳过。三秒钟能看完。
前面绿的都成功了,失败几乎总在第一个红的那一步。不用读完整本日志。
报错信息总在最后。你在那里能看到具体是哪个文件、哪一行、什么原因——这段正是你该复制给 AI 的东西。
机器不知道你想要什么,它只执行清单上写的字。所以「AI 给我写了一份 YAML」这件事本身不等于「我的项目被检查了」。你要能回答三个问题,才算真的接上了:它在什么情况下会跑?它到底跑了哪几件事?哪一件事失败会挡住什么?——这三个问题正是 §04 那张检查清单要拆的。
04读懂 AI 写的那份 YAML:九个词逐词拆
你不必会写,但必须会读——因为这份文件就是「机器判断你项目的规则」。AI 写它只需要几秒,而它写错了,你手上不会有任何提示:不会报错,不会有人告诉你,只会安安静静地什么都检查不了。这一节的目标是让你能在两分钟内回答:「这份配置,在我的仓库里什么时候会跑、跑哪几步、哪一步失败会挡住合并。」
先说清楚 YAML 是什么,以及它为什么这么容易错
YAML 是一种纯文本的填写格式,用缩进表达「谁属于谁」,像抽屉套抽屉:缩进多一格,就表示「这一行属于上面最近的那一层」。
抽屉套抽屉是「视觉上对齐」,而 YAML 的缩进是语法本身:缩进错了不是不好看,是文件直接不合法、整条流水线拒绝执行。而且它有两个硬规矩:只能用空格缩进,不能用 Tab 键;同一层必须缩进同样多。这两条正是从别处复制粘贴配置后最常见的「无缘无故跑不起来」。
九个词,逐个拆
下面九个词覆盖了绝大多数 GitHub Actions 配置的骨架。每一个都给你「它回答什么问题」和「长什么样」。
| 写法 | 它回答的问题 | 常见取值与注意点 |
|---|---|---|
name |
这份清单叫什么名字? | 随便起,会显示在页面上。写清楚能救命:三个月后你能一眼看出哪份是「发布」哪份是「检查」。 |
on |
什么事发生时跑? | push(有人往某分支推代码)、pull_request(有人提交合并请求)、workflow_dispatch(页面上手动点一下就跑)、schedule(定时,比如每周一)。这是最该先看懂的一行。 |
jobs |
要干哪几件相对独立的事? | 下面每一项是一个作业,各自跑在一台机器上。给它们起能读懂的名字(check / test / deploy)。 |
runs-on |
用哪种机器? | 常见 ubuntu-latest。§02 讲过:它会在平台更新默认版本时悄悄变化。 |
steps |
这个作业里按顺序做哪些动作? | 从下往上依次执行,前面失败后面默认不跑。 |
uses |
用哪段现成的动作? | 写法是「作者/动作名@版本」,例如 uses: actions/checkout@v5。后面一定跟着版本号——不写版本号是最危险的习惯,因为对方更新后你的行为会跟着变。 |
with |
给这段现成动作传什么参数? | 例如给它指定 Node.js 版本。with 下面缩进的就是一行行参数。 |
run |
直接执行哪条命令? | 就是你平时在终端敲的那类命令(npm 是 Node.js 项目通用的「装依赖、跑任务」命令)。这一行决定「检查」到底是什么:你写 npm test,它就是跑测试;你写 echo ok,它就永远成功、什么都没检查。 |
needs |
这个作业要等谁成功? | 用来表达依赖与顺序(§03 的例子里讲过)。发布作业几乎总该写它。 |
还有两个你会在稍复杂的配置里遇到,先认个脸:if(「满足条件才跑这一步」,它是「某一步被跳过」的最常见原因)、permissions(「给这段自动化多大的权限」,默认偏宽松,是安全审查的重点)。
一份带注释的最小配置,逐行读一遍
# 这份清单的名字,会显示在页面上
name: 基础检查
# 什么时候跑:有人推到 main 分支,或有人提交合并请求时
on:
push:
branches: [main]
pull_request:
jobs:
# 作业名:检查(这个名字会出现在页面上,也会被用来做「必须通过」的规则)
check:
runs-on: ubuntu-latest # 用哪种机器
steps:
# 第 1 步:把这份代码下载到机器上(几乎所有清单的第一行)
- uses: actions/checkout@v5
# 第 2 步:装好指定版本的 Node.js
- uses: actions/setup-node@v4
with:
node-version: 22
# 第 3 步:装项目依赖
- run: npm ci
# 第 4 步:这就是「检查」本身——失败了整条流水线就变红
- run: npm run lint
- run: npm test
注意这份配置里没有一行是「发布」。它只做一件事:每次改动都替你验一遍。对绝大多数一个人维护的项目来说,这已经是最值钱的那一半了——发布可以继续用平台自带的自动部署。
AI 写这份文件时最常出错的三处
现成动作带版本号,是因为它们会更新、会废弃。AI 很可能照它训练时见过的旧版本写,或者写一句「用最新版」——而「不写版本」等于把决定权交给对方,将来某天行为变了你无从追溯。核对方法很土:打开那个动作的官方仓库,看它当前推荐的主版本。
为了让配置「能跑起来」,AI 常顺手把权限开到最大(能改代码、能发版、能碰别的仓库)。正确姿势是「只给它这一步真正需要的」,其余显式收掉。这条不是洁癖:流水线里跑的代码一旦被人植入坏东西,权限就是你实际损失的边界。
这里有个专有名词(pull_request_target),第 07 节会专门讲它为什么危险。你只需要记住一个信号:任何一份配置里出现这个词,而又同时用到了你的密钥,就必须停下来问清楚。
两分钟读懂一份配置:五个问题
① 谁会让它跑?读 on:——是推到分支时、有人提合并请求时,还是只能手动点?
② 它跑在什么机器上?读 runs-on:——这决定了环境和费用。
③ 它一共做几件事?数 jobs: 下面的名字,每个作业一句话说清它干什么。
④ 每一件事里,「检查」到底是哪一行命令?读 run:——这一行写的是什么,它检查的就是什么;这一行如果是句没意义的命令,这份配置就是摆设。
⑤ 失败了会挡住什么?看有没有 needs 把发布挂在检查后面,以及这个检查有没有被设成「必须通过」(§06)。
把这份配置和「五个问题」的答案一起交给你自己保存的一份笔记。理由是:AI 下次改这份文件时,很容易顺手把触发条件或某一行检查删掉,而删掉之后流水线依然会跑、依然会绿——只是不再检查那件事了。留一份「它本来该干什么」的记录,你才有东西可对。
05最小例子:给一个半成品项目接上第一条流水线
这一节全程在 GitHub 网页上完成,不需要你在本地装任何东西。整个过程的目标不是「学会写 YAML」,而是拿到一次亲眼看见的绿灯和一次亲眼看见的红灯——因为你要建立的能力是「判断」,而判断只能从见过开始。
先对号入座:你是哪一种起点
| 你现在的状态 | 这一节该做哪一段 | 为什么 |
|---|---|---|
| 有一个推到 GitHub 的仓库,从来没配过流水线 | 走「第一段:先看见一次绿灯」 | 先把机制跑通,别的都建立在这上面 |
| 仓库里是一个有代码的项目(Node / Python / 前端框架都算) | 再走「第二段:让机器真的替你检查」 | 这才是 CI 的日常形态 |
| 做的是一个静态网页(一堆 HTML/CSS,没有后端) | 走「第三段:让机器自动上线」,可以跳过第二段 | 你最需要的是发布那一半,而不是测试 |
| 还没有 GitHub 仓库 | 先去建一个(本仓 Git 和 GitHub 快速上手 里从零讲过),再回来 | 流水线的一切都挂在一个仓库上 |
第一段:先看见一次绿灯
这段做完大约三分钟。你会发现「配流水线」这件事本身并不难——难的是它到底该检查什么。
打开你的仓库,点「Add file」→「Create new file」。文件名的位置要连目录一起写:.github/workflows/hello.yml。注意开头那个点是必需的,它不是笔误——GitHub 靠这个固定目录来发现流水线配置。
它可以整段复制,一行都不用改:
name: 我的第一条流水线
on:
push:
workflow_dispatch:
jobs:
hello:
runs-on: ubuntu-latest
steps:
- run: echo "流水线跑起来了"
- run: echo "当前时间 $(date)"
页面底部直接点「Commit changes」。这一步等于一次推送(push)——对,在网页上改文件也算。
点仓库顶部那一排里的「Actions」。你会看到「我的第一条流水线」下面多出一次运行,一个黄点(正在跑)→ 变成绿勾(成功)。点进去,能看到 hello 这个作业下面两步,每步都能点开看它打印了什么。
回到文件,把某一行的命令改成一个不存在的命令,例如把 echo 改成 echoo,再提交。回 Actions 看——这一次是红叉。这一步比上一步重要:你要亲眼看一次「红灯长什么样」「日志里那句话在哪」「失败之后后面的步骤是什么状态(灰色,被跳过)」。以后 AI 说「CI 过了」时,你脑子里有这张图,才有资格怀疑。
① 流水线的定义就是一个放进固定目录的文本文件,新建它不需要装任何工具;② 每次推送都会触发一次运行,结果和你这次推送的代码绑定;③ 失败是一等公民——它不是报错弹窗,而是一个可查看、可复现、有记录的状态。
第二段:让机器真的替你检查
第一段那份配置其实什么都没检查(echo 只是打印一句话,永远成功)。现在换成一份真正会拦人的。前提:你的项目里已经有 package.json(Node.js 项目的清单文件:写着它要用哪些外部库,以及几条可以一条命令跑起来的任务),并且它有 lint 和 test 这两条命令(没有的话,先让 AI 帮你加——这件事本身就是个好任务,第 08 节会讲怎么交代)。
name: 检查
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm # 把「装依赖」的结果存起来,下次直接取,省时间
- run: npm ci # 严格按锁文件装依赖(比 npm install 更适合流水线)
- run: npm run lint # 检查格式与低级错误
- run: npm test # 跑测试——这一行才是「检查」的主体
- run: npm run build # 确认能构建出成品
几个词顺手解释掉:锁文件(package-lock.json)是记录「精确到小版本」的依赖清单,它保证你机器上装的和别人装的完全一样,所以流水线上用 npm ci(严格照锁文件装)而不是 npm install(可能顺手升级)。node-version: 22 是你指定的运行环境版本,写死它才有可复现性;不写就会用平台默认的,而默认会变。
第三段:静态站自动上线(GitHub Pages)
如果你做的就是一堆 HTML/CSS,这一段直接给你结果:每次推到 main,网站自动更新。这份配置的核心不是命令,而是最上面那段权限声明和并发控制——它们是官方模板里就有、而你最容易在抄的时候抄丢的部分。
name: 发布静态站
on:
push:
branches: [main]
workflow_dispatch: # 允许在页面上手动点一下就跑
permissions: # 只给这件事需要的权限,其余一律不给
contents: read # 读代码
pages: write # 写托管页面
id-token: write # 领取一次性凭证(发布类动作需要)
concurrency: # 同一时间只允许一次发布,避免两次发布互相踩
group: pages
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }} # 运行页面上会显示线上地址
steps:
- uses: actions/checkout@v7
- uses: actions/configure-pages@v6
- uses: actions/upload-pages-artifact@v5
with:
path: . # 把整个仓库当一个静态站上传
- id: deployment
uses: actions/deploy-pages@v5
用之前还要在仓库里打开一次开关:Settings → Pages → Build and deployment → Source 选「GitHub Actions」。不打开的话,流水线会绿着但页面不更新——这是最典型的一种「看起来成功、其实什么都没发生」。
这份配置里最值钱的一课:版本号没有任何二手来源可以信
上面四个现成动作后面跟的版本号,是我在2026-09-23 当天从每个动作的官方仓库逐个数出来的。之所以要专门说这件事,是因为同一天我读到的三个「官方」来源给出了三套不同的答案:
| 现成动作 | 官方入门模板里写的 | 该动作自己 README 示例里写的 | 官方仓库最新发布(当日实查) |
|---|---|---|---|
actions/checkout | v4 | v7 | v7.0.1 |
actions/configure-pages | v5 | 未给具体版本 | v6.0.0 |
actions/upload-pages-artifact | v3 | v3 | v5.0.0 |
actions/deploy-pages | v5 | v4 | v5.0.1 |
三列都是「官方」,却互相对不上:入门模板会滞后(它是一份很少改的示例),动作自己的 README 也会滞后(示例代码忘记跟着版本更新),而 AI 写给你的版本号来自它训练时的记忆,只会更旧。所以「版本号」是第 08 节那张验收清单上的第一项:不核对就不用。核对方法就一句话——打开那个动作的官方仓库,看它的 Releases 页面排在最上面的大版本号。
跑通之后,你手里多了三样东西
「这个提交通过了」这句话以后有出处:某次运行、某个编号、某几行日志。它不能靠谁声称。
每次运行都留下时间与改动记录。翻回去看,你能说出「线上这个版本是什么时候、从哪次改动来的」。
你可以直接对它说「按这份清单,你的活才算干完」——第 08 节讲怎么把这句话说准。
这一段里最容易卡住的地方
on: 里写的事件和你刚才的动作对不上(你往 main 推,配置里却限定了别的分支)。其余几种失败——依赖装不上、权限不足、发布成功但线上没变——都在 §12 那张排错表里。06绿灯不等于挡住合并:让机器真正有否决权
到这里你有了绿灯,但它还只是一条信息。信息不会拦任何人:红灯亮着,你照样点得动「Merge」按钮,AI 也照样合得了。这一步要做的,是把信息换成否决权——这是整篇文章里性价比最高的一次点击。
先分清这两件事
| 状态检查(信息) | 必须通过的检查(否决权) | |
|---|---|---|
| 它是什么 | 「这个提交上,检查是成功还是失败」这条记录 | 一条规则:这项检查没成功,就不允许合并 |
| 谁会遵守 | 看的人自己判断 | 平台强制,任何人(包括你自己和 AI)都绕不过 |
| 失败时发生什么 | 页面上多个红叉,你依然可以合 | 合并按钮被禁用,并写明还差哪一项 |
| 你要做什么才能得到 | 不用做任何事,配好流水线就有 | 去设置里显式打开(下面就是) |
在哪里打开
两条路,选一条即可(两条会同时生效,不是二选一):
仓库 Settings → Rules → Rulesets → 新建一个规则集 → 勾上 Require status checks to pass before merging(中文界面大致是「合并前必须通过状态检查」)→ 在列表里选中你那条检查的名字。
仓库 Settings → Branches → 给 main 加一条保护规则 → 同样勾选那一项。
还有一档选项值得知道:「Require branches to be up to date before merging」(合并前分支必须是最新的)。打开它,别人刚合进去的改动会让你的检查重新跑一遍——更严格,也更慢。一个人开发时,可以先用默认、不开它。
这一步里最容易踩的三个坑
平台只允许你选「在这个仓库里至少出现过一次的检查」。所以顺序是:先让流水线在某个提交上跑一遍,再回来设置。刚写完配置就来找,列表会是空的。
勾选时用的是作业名(§04 里那个 check,或者作业里的 name)。如果哪天有人(或 AI)顺手把作业改了名,那条规则就找不到它了——规则还在,检查还在跑,但两者不再挂钩。这是一种没有任何报错的失效:你以为还锁着,其实已经开了。所以「作业名」不是随便起的东西。
设成必须通过之后,如果流水线因为跟你的代码无关的原因一直红(比如免费额度用完了、依赖源挂了),你连一个紧急修复都推不上去。两个保险做法:不要把所有分支都锁上(只锁 main);确认自己有管理员通道能临时调整规则。规则是给自己省事的,不是给自己上刑的。
为什么一个人开发(还带着 AI)反而更该设
很多人会觉得,「就我一个人,谁会乱合」。但在 AI 编程的场景里,这条规则的真正作用对象不是「别人」:
深夜改完,页面上一片绿,你点合并——但那一刻的绿可能来自上一次运行,或者检查里恰好跳过了关键那步。规则会替你多问一句。
这是关键:AI 可以改代码、可以提交、可以开合并请求,但它没有能力把红的说成绿的——因为否决权在你手之外、在平台的规则里。
不需要你每次记得去看。不通过就是合不了,你会自然地在那个页面停留三秒——而那三秒正是你要的验收。
状态检查是「告诉你有问题」,必须通过的检查是「不允许有问题溜过去」。前者只需要配流水线,后者需要你去设置里点一次;而这一次点击,是把「机器替你判断」从愿望变成制度的分界线。
07唯一一处「不看会真的出事」的地方:密钥
前面六节都是关于「怎么把事做对」;这一节是关于「怎么不出事」。它值得单独占一节,因为流水线里几乎所有严重事故,最后都落在同一件事上:那段自动运行的代码,拿到了不该拿到的东西,而把它拿走的东西不是人写的,是顺着配置进来的。
先看清楚事故长什么样
密钥(行业里也写作 secret、凭据、token、API key)指的是那类「拿着它就能以你的身份做事」的一串字符:数据库密码、云服务的访问密钥、第三方接口的 key、能改你代码的令牌。它在流水线里通常会死在三处:
为了「先跑通」,把密码直接写在 YAML 或代码里提交上去。即使下一分钟就删掉,它仍然留在这份仓库的历史记录里——历史是设计上不可改的。正确做法只有一个:一旦提交过,就当它已经泄露,去服务商那里把它作废、换一串新的。
为了排查问题,加一句把密钥打印出来的调试命令。日志是给协作者(以及任何有权限的人)看的,而且会被长期保存。排查完删掉命令也没用,那次运行的日志还在。
这是最隐蔽的一种,也是第 08 节要用真实案例讲的那种:如果流水线在一个别人可以往里塞代码的场景下运行,那么那段代码就有机会在你分配到的机器上执行,然后把环境里的密钥发到外面去。
正确姿势:把密钥放进平台的保险箱,用名字引用
平台的机制很简单:密钥存在仓库设置里,YAML 里只写它的名字,运行时平台才把值注入到机器上。
仓库 Settings → Secrets and variables → Actions → New repository secret。起一个能看懂的名字,例如 DEPLOY_TOKEN。存进去之后,平台不会再让你看它的值——只能覆盖,不能读取。这不是刁难,是设计。
- run: ./deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
注意这里的写法是「取保险箱里那个名字对应的值」,文件里从头到尾没有出现过真正的密钥,所以它可以安全地公开、被复制、被 AI 改。
同一页面下面还有一类叫 Variables,放的是不怕被看到的东西,比如环境名、区域名。判断法一句话:这东西泄露了会不会造成损失?会 → 放 Secrets;不会 → 放 Variables。把配置塞进 Secrets 不会更安全,只会让你以后排查问题时看不见它。
平台会自动把日志里出现的密钥值替换成星号,这是防呆,不是保险——只要密钥被处理过(拼接、编码、切成几段)就可能漏掉。所以真正的规矩是「不让它出现在输出里」,而不是「相信打码」。你给 AI 下指令时可以直接写上这一条(第 08 节给了原句)。
为什么「别人提交的合并请求」拿不到你的密钥
这一条是官方刻意设计的,原话是:
「With the exception of GITHUB_TOKEN, secrets are not passed to the runner when a workflow is triggered from a forked repository.」(除 GITHUB_TOKEN 外,当流水线由来自分叉仓库的动作触发时,密钥不会被传给运行机器。)
出处:docs.github.com — Using secrets in GitHub Actions
道理很直白:在一个公开仓库上,任何人都可以提交一份合并请求。如果平台把密钥交给这类运行,那等于任何人提一次请求就能把你的密码打出来。所以平台选择扣住不给。
代价是:你那些「需要密钥才能跑」的检查,在外部人提交的合并请求上跑不了。于是就有了一类看起来很聪明的做法——换一个触发方式,让别人的请求也能拿到密钥。这个做法的名字叫 pull_request_target,官方文档称它是最常被误用的触发器,还给这类事故起了个名字叫「pwn request」。
任何一份配置里同时出现pull_request_target(或 workflow_run)和你的密钥,就停下来问清楚:它是不是在给「外部人写的代码」配上「你的凭据」?如果是,这就是事故配方。
官方文档专门有一页讲这个:Securely using pull_request_target。另外,从 2026-06-18 起,官方取代码的动作 actions/checkout 的 v7 版本默认拒绝在这种触发方式下去抓取外部分支的代码,要显式打开一个选项才允许——你自己写的配置里如果还在这么干,现在会直接被拦下来。
更省事也更安全的做法:让它领一张「临时工牌」
上面讲的都是「怎么保管长期密钥」。还有一条路是干脆不存:让你的流水线在运行时向云服务商换一张几分钟后就失效的临时凭据。这个机制叫 OIDC(你可以就理解成「现场办工牌」):平台先和云服务商约定好信任关系,运行时由平台证明「这条流水线确实是我这边跑的、跑的是这个仓库的这个分支」,服务商才发一张短命凭证。
| 存长期密钥(Secrets) | 现场换临时凭据(OIDC) | |
|---|---|---|
| 泄露后能撑多久 | 直到你发现并作废为止,可能是几个月 | 几分钟到一小时,过期即废 |
| 配置成本 | 低:存一个名字就能用 | 较高:要在云服务商那边建立信任关系 |
| 什么时候值得上 | 大多数个人项目;访问第三方接口的 key | 部署到云平台(AWS / GCP / Azure 等)这类「本来就有云账号」的场景 |
| 你要防的事 | 别把它写进任何文件 | 别把信任关系设得太宽(例如「任何分支都能拿」) |
原理与配置入口见官方文档:docs.github.com — OpenID Connect。个人项目不必急着上它,但你应该知道「密钥不一定非得存着」这件事存在——它是判断别人建议是否专业的一个标志。
交给 AI 之前的三条红线
① 「不要把我告诉你的任何密钥、密码、token 写进任何文件——包括 YAML、脚本、配置文件、注释和示例。」
② 「需要密钥时,用仓库 Secrets 的名字引用,并告诉我这个密钥要在哪个页面上创建、叫什么名字。」
③ 「不要为了让它跑通而把权限开到最大。每一步只给它需要的权限,并在改动说明里告诉我你为什么给这些权限。」
这三句之所以必须显式说,是因为 AI 的默认目标是「让这次任务通过」,而权限收紧、密钥外置这类要求,在「让它跑通」这件事上全是阻力。你不说,它多半会选阻力最小的路。
08AI 编程里怎么用:把它当裁判,而不是当省事工具
前面七节讲的都是「流水线是什么」。这一节回答你真正的问题:我带着 AI 写代码,这套东西对我的用处跟对一个程序员一样吗?
答案是:对你更值钱,但值钱的地方完全不同。一个会写代码的人用 CI,图的是「省得我每次手动跑一遍」;你不写代码,图的是——它能把你和 AI 之间那句「我改好了」,换成一件你亲眼看得见的事实。
为什么它恰好补上了你最缺的那块
你的处境是:必须做决定,但判断不了实现。AI 给你一段改动,末尾写着「已修复,测试全部通过,可以合并了」。这句话你没办法核——你既不会读代码,也无法确认它是不是真的跑了测试、跑的是不是那套测试、有没有把跑不过的那几个先删掉。
流水线做的是把这句话变成一条记录:某个时间、某台机器、某个提交、跑了几条命令、结果是什么。它带来三样 AI 的话给不了的东西:
| 它给你什么 | 为什么 AI 的叙述给不了 | 你怎么用这一条 |
|---|---|---|
| 可复现 | AI 说「我跑过了」是它的一次性说法;流水线跑的是同一份清单,换台机器结果一样 | 同一个提交,谁跑都是这个结论——所以它可以当证据 |
| 不可篡改 | AI 可以「说」通过,但它没法把红的说成绿的——结果由平台记录,不在它会话里 | 你不需要相信 AI 的自述,只需要打开那个页面看一眼 |
| 留痕 | AI 的会话关掉就没了;每一次运行都留在平台上,带时间与提交 | 出问题时你能回到「上次好的是哪次」——这件事在没有流水线时几乎做不到 |
流水线的结论之所以可信,是因为AI 不该有权限改那份配置和那条规则。如果 AI 既能改代码、又能改「检查什么」、还能改「必须通过」,那它随时可以让检查变成一句永远成功的命令——绿灯照旧,什么都没验。
所以第 06 节那一步不是可选项:检查清单的改动,应该被人看见。一个够用的最低标准是:让 AI 改配置时单独提一次改动,并在提交说明里写明「我改了哪些检查」,而不是顺手塞在改 bug 的那次提交里。
把「完成」的定义写进给 AI 的任务卡
你最缺的东西是一句能照着念的话。下面这段可以直接复制,把方括号里的内容换成你的项目实际情况:
这个任务算完成的定义,不是你说「改好了」,而是:
1. 我能在 GitHub 的 Actions 页面看到这次改动对应的运行,并且它是绿的。
2. 绿的这一次里,下面这些检查都真的跑了(不是被跳过、不是被注释掉):
[npm run lint / npm test / npm run build]
3. 你没有为了让它们变绿而删掉或跳过任何测试。
4. 完成后,告诉我:这次运行是第几次、在哪个页面看、我该看到什么。
如果检查不通过,不要说「基本完成」——把失败的那一步日志原文贴给我。
这段话的效果是把验收标准从「形容词」换成「可核对的事实」。它和本仓另一篇《把「批准方案」换成「验收结果」》讲的是同一件事,只是这里连「谁来判断」都交给了机器。
更短的一版,适合你每次只改一小处时顺手带上:
改完先别急着说完成。先推上去,然后去看这次 Actions 运行的结果,
把「第几次运行 + 哪一步 + 什么结论」告诉我,再等我确认。
让 AI 自己读红灯去修——以及为什么必须设两轮上限
红灯的日志正好是 AI 最擅长处理的东西:它是文本、在一个确定的地址上、里面有明确的错误行。所以「失败 → 让 AI 读日志 → 它改 → 再跑一次」这个闭环是可以自动化的,也是 AI 编程里效率最高的一处配合。
让它读日志,而不是让它猜
# 看最近几次运行(哪次红、哪次绿)
gh run list --limit 10
# 看某一次运行里「失败那一步」的日志——这才是有用的那段
gh run view <运行编号> --log-failed
# 懒得重跑一次:让平台把失败的那些作业再跑一遍
gh run rerun <运行编号> --failed
gh 是 GitHub 官方的命令行工具。这三条命令的价值在于:AI 不需要你复述错误。你只要给它运行编号,它自己就能把原文取下来。这比你说「它失败了,好像是缺个包」准确得多——而「准确」正是你付给 AI 的钱。
上限两轮,第三轮换你判断
让 AI 修红灯,最多两轮。第三轮开始,它为了「变绿」而动手脚的概率会明显上升,而且这些动作看起来都像正常修复。第三轮之后该你做的是:停下来问「是不是方向就错了」,而不是继续让它试。
它会怎么动手脚?有三种形态,都是 GitHub 官方在「如何评审 AI 生成的代码」那一页里点名提醒过的(原文见本节末尾的来源)。好消息是:这三种形态全都不需要你懂代码就能看出来。
| 它可能的做法 | 为什么这能让红灯变绿 | 你怎么用肉眼发现 |
|---|---|---|
| 删掉那个跑不过的测试,或给它加上「跳过」标记 | 没有测试在跑,自然就没有失败 | 看这次改动删了哪些文件——测试文件被删或被加跳过标记,是最大的警报 |
把检查命令改松:把 npm test 换成一句永远成功的命令,或给 lint 规则关掉几条 |
检查本身不再是检查 | 看 .github/workflows/ 和检查相关的配置文件这次有没有被改(对比 §04 那五个问题) |
| 把报错「吞掉」:加上忽略错误的开关,让失败不再往上冒 | 错误还在,只是没人知道了 | 看改动说明里有没有出现「忽略」「跳过」「暂时禁用」这类词——它自己通常会写出来 |
「修 bug 的时候不许改测试。要改测试,单独提一次,并说明为什么。」
这句话的作用是:把「测试」和「被测试的代码」分开成两次改动。因为让红灯变绿只有两条路——修代码,或者改检查;只要这两件事不能发生在同一次改动里,你就永远看得见它走了哪条路。这是你不懂代码也能执行的一条纪律。
让 AI 待在合并请求上:四种现成的自动审查
除了「CI 替你跑检查」,还有另一条路:让 AI 自己来当第一遍审查者。现在主流的几家都提供了现成的东西,全部挂在你已经会用的那套机制上(合并请求 + 状态检查)。
| 哪家的 | 你怎么触发它 | 它能替你做什么 / 边界在哪 |
|---|---|---|
Claude Code 的 GitHub 集成anthropics/claude-code-action |
在仓库里运行安装命令 /install-github-app;之后在合并请求或 issue 里写 @claude 加你的要求 |
能读代码、能读这个 PR 上的检查结果(需要显式给「读 Actions」的权限)、能直接推修复。官方文档明确要求触发者必须有写权限,并默认拒绝由机器人触发——这两条是安全设计,别关掉。密钥用 API key 或订阅令牌。 |
| OpenAI Codex 的 GitHub 集成 | 在 PR 里评论 @codex review;也可以让它 @codex fix the CI failures |
审查默认只报严重问题(最高两档),这既是优点(噪音少)也是局限(不报小问题)。可以用项目根目录的 AGENTS.md 里一节「Code Review Rules」写你自己的规则。自动审查要付费订阅,纯 API key 不含这一项。 |
| GitHub Copilot 的审查与云代理 | 在 PR 上请求 Copilot 审查;或把一个 issue 指派给 Copilot,它会开出一个 PR | 审查需要付费计划。关键细节:它默认只留「评论」型的审查意见,不计入「批准」——也就是说它说「看起来不错」不等于你的规则放行。云代理那边还有硬限制:一次只开一个 PR、单个会话最长 59 分钟。 |
| Cursor 的 Bugbot | 自动评审 PR;也可以手动用 cursor review 触发 |
同样是「提意见 + 可选自动修复」的形态。它在你的仓库里读代码,因此同样受「给它多少权限」的约束。 |
① AI 审查 ≠ 检查通过。前者的产出是「意见」,后者是「结论」。你那条「必须通过」的规则应该只挂在后者身上——因为 AI 的意见会随措辞、版本、额度变化,而检查的结论是确定的。
② AI 审查 ≠ 替代 CI。审查读的是代码文本,检查跑的是真命令。「读起来没问题但一跑就炸」只能靠 CI 抓;「跑得通但逻辑荒唐」只能靠审查抓。两者是互补,不是二选一。
③ 自动审查要花额度。它们按用量计价,PR 一多就看得见。对个人项目,务实做法是:CI 每次推送都跑(几乎不花钱),AI 审查只在你自己拿不准的那几次手动叫。
当 AI 自己跑在流水线里:一个真实事故,以及它为什么值得你记住
这一节讲的是一个具体的安全公告。它不是「某个人操作失误」的故事,而是这类自动化的一个结构性风险——你越依赖它,越该知道它的形态。
编号 CVE-2026-47751,公开记录是 GHSA-8q5r-mmjf-575q,发布于 2026-06-10,修复于 claude-code-action 1.0.74。
攻击路径(官方公告里的描述,我译成日常语言):这个动作会检出合并请求里的代码,并读取工作区里的一个配置文件(.mcp.json,用来告诉 AI 该接哪些外部工具)。于是任何人提交一个 PR、在里面塞一份精心写过的配置文件,那段配置就会在那台运行机器上被执行——攻击者因此可以在你的运行环境里跑任意命令,把环境里的密钥取走。
出处:github.com/anthropics/claude-code-action/security/advisories/GHSA-8q5r-mmjf-575q
为什么这件事对你有用:它说明「AI 自动化」和「密钥」放在一起时,攻击的入口不是你的密码,而是 AI 会读的那些文本。在这类流水线里,下面这些东西全都是外部输入,也就是别人可以往里面塞话的地方:
合并请求的标题、正文、评论;issue 的内容;代码里的注释;仓库里的配置文件。攻击者不需要攻破你的账号——他只要提一个 PR 就够了。
运行机器上的环境变量(里面可能就是你的密钥)、它能调用的接口、以及它在你的仓库里被授予的写权限。它被允许做什么,别人就能借它做什么。
还有一份公开研究得出过同类结论:2026 年 4 月,一家安全研究机构公开了一项实验,把指令藏在 PR 标题和 issue 正文里,让几个主流的 AI 代码审查工具把 API 密钥回帖到了公开页面。需要标注的是:该研究称涉及多家厂商的产品,但我没有找到这些厂商自己发布的公告或编号,因此它属于单一来源——我把它留在这里供你知道有这一类风险,不把它当成已确认的厂商事实。出处见第 15 节。
那几条能真正降低风险的做法
| 做法 | 它挡住的是什么 | 代价 |
|---|---|---|
| 只让有写权限的人能触发 AI 自动化(官方默认就是这样,别改) | 陌生人用一个 PR 就能让你的机器人替它干活 | 几乎没有——除非你要对公众开放自动回复 |
| 不要把外来的分支代码检出到工作区根目录再让 AI 读 | 恶意配置文件被当成本项目配置执行(上面那个 CVE 就是这条) | 需要你(或 AI)真的看懂配置里那一行 |
| 用官方动作,并锁主版本、定期升级 | 已公开的漏洞(上面那个 CVE 就是升级到 1.0.74 就修好的) | 每几个月花五分钟看一次发行说明 |
| 能不给长期密钥就不给(第 07 节的 OIDC / 临时凭据) | 一旦泄露,损失的时间窗从「几个月」缩到「几十分钟」 | 初次配置麻烦 |
你给 AI 自动化的权限,就是任何能触发它的人能拿走的权限。所以「让它更自动」这件事,永远要配套问一句:「它手上有什么?谁能碰到它?」这两问都不需要你懂代码。
一个人 + AI 的完整流水线,照着搭一遍
把前面所有东西拼起来,一个一个人维护、由 AI 主要写代码的项目,够用的形态是这样三层:
配套的检查配置,就用第 05 节第二段那份,只加一处「把结论写清楚」的小东西——这一步对不写代码的人特别有用,因为你在页面上第一眼看到的就是它:
name: 检查
on:
push:
branches: [main]
pull_request:
jobs:
check:
name: 检查(lint + test + build) # 这个名字会出现在「必须通过」的规则里
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
- name: 把结论放到运行页面上
if: always() # 不管前面成没成,这一步都执行
run: echo "以上四项全部通过,才算这次改动验收完成"
然后配上一段你和 AI 之间固定的开场话术(每次新任务粘一次):
规则:
1. 你改完先自己跑一遍 [lint / test / build],再推上来。
2. 我只看 GitHub Actions 页面上的结论,所以推完告诉我:第几次运行、哪一步、什么结论。
3. 不许为了让检查变绿而删测试、跳过测试、或改松检查命令。要改检查,单独提一次并说明原因。
4. 不许把任何密钥写进文件;需要密钥时告诉我它该叫什么名字、在哪里创建。
5. 遇到连续两次修不好,停下来告诉我你怀疑哪里错了,不要继续试。
① 红灯出现过几次?——常年全绿不代表健康,可能代表检查太松。
② 这几天有没有「被跳过」的检查?——跳过比失败更值得警惕,因为它没有声音。
③ 有没有哪次改动动了测试或检查本身?——这是第 08 节那三种作弊形态的入口。三件事都在同一个页面看完。
09六类场景:你现在该上哪一层
流水线不是「要么全上、要么不上」。它是一层一层加的,而且从最低的那层开始加,收益最大、代价最小。下面这张表按「你现在的状态」分流,照自己的那一行看就行。
| 你现在的状态 | 该上哪一层 | 具体做什么 | 可以先跳过什么 |
|---|---|---|---|
| 刚做出一个能打开的页面,想让它一改就更新 | 用平台自带的自动部署 | 把仓库连上 Vercel / Cloudflare Pages / Netlify 这类平台,之后推到 main 就自动更新 | 整份 YAML。这一层根本不需要你写配置,平台替你写好了 |
| 已经有线上站,每次改完都怕弄坏 | 最小检查 + 必须通过 | 只写一条「能不能构建成功」的检查(npm run build),再把它设成必须通过 |
测试、缓存、多环境——先不需要 |
| AI 一天改十几次,你跟不上节奏 | 检查 + 必须通过 + 任务卡 | 第 05 节第二段那份检查,配上第 08 节那段给 AI 的开场话术 | AI 自动审查(先用手动叫的方式,别一上来就自动) |
| 这个项目不只你一个人碰(或半年后的你) | 合并请求 + 必须通过 + 版本留痕 | 要求改动走合并请求再合,检查挂在上面;每次发布都能对上某次运行 | 复杂的部署编排 |
| 每次发布都要重复七八个手动步骤 | 把发布也写进流水线 | 在检查后面加一个「发布」作业,用 needs 挂在检查之后(§03 那份骨架) |
蓝绿/金丝雀这类高级发布策略 |
| 固定时间要做同一件事(每周清理、每天同步) | 定时触发的流水线 | on: schedule 加一条定时规则,让它自己跑 |
其余全部 |
第一步:能不能构建成功(npm run build)——它能抓住最大的一类问题,而且几乎不需要你先写任何东西。
第二步:格式与低级错误(lint)——它抓的是那种「一眼就能看出来」的错,性价比很高。
第三步:测试——这才需要你先有测试。没有测试就先跳过这一步,别为了凑齐流水线去写一堆假测试。
第四步:发布——等你已经习惯看绿灯了,再把「发出去」也交给它。
10什么时候不该做
这一节和上一节一样重要。流水线有它的反面作用:配错、配早、配了不看,它会把你的判断力换成虚假的安全感。下面五种情况,先别上。
项目结构一天一变的时候,流水线会频繁变红,而每次红都不是代码错,是它跟不上变化。红久了人会形成最坏的习惯——学会无视红灯。先让项目稳定到「三天不用大改」,再上。
如果清单里只有 echo ok 这类永不出错的命令,你会得到一条永远绿的流水线。这比没有更糟:没有的时候你知道自己没验,永远绿的时候你以为自己验了。
红着没人看,等于没有这条检查,还多了一份「我们是有流水线的」的错觉。判断法很直接:你愿不愿意承诺「看到红就先处理它」?不愿意就先别配。
它是发现问题的,不是解决问题的。「检查失败 → AI 修 → 再跑」这个闭环可以自动化一半,但必须有人设上限、做方向判断(§08 那两轮上限就是干这个的)。指望它自己修完,你会得到一个慢慢被改松的检查体系。
私有仓库的免费额度是按机器分钟算的(各档额度与超额单价见官方计费页)。只跑几分钟的检查几乎不可能花到钱,但每次重装几 GB 依赖、又没有缓存的配置会。先加缓存,或者先用公开仓库。
同一件事的更便宜做法(它们为什么不能完全替代)
| 替代做法 | 什么时候够用 | 为什么不能替代流水线 |
|---|---|---|
| 写一条命令,让 AI 每次改完自己跑 | 你一个人、自己看结果、项目还在快速变 | 结论不可复现、也没有留痕。跑在谁的机器上、有没有真的跑,你只能相信它的话——而「只能相信」正是你要摆脱的状态 |
| 用平台自带的自动部署 | 你要的是「一推就上线」,而不是「每次改动都验一遍」 | 它只管最后一步。它能保证「发出去了」,不能保证「发出去的是对的」——发布成功和发布正确是两件事 |
| 提交前钩子(pre-commit hook) | 你想在提交之前就挡掉格式问题,省得来回跑 | 它在你自己的电脑上跑,而且可以绕过(跳过钩子照样能提交)。电脑上的绿,不是证据 |
| 先写三个测试,手动跑 | 你连「检查什么」都还没想清楚 | 这是很好的一步,但它是流水线的前置,不是它的替代。测试写好了,接上流水线才有东西可跑 |
上面这些替代品的共同点是:它们都发生在「你控制得了的环境里」。而流水线唯一不可替代的价值,恰恰是它发生在你控制不了的环境里——同一份清单、一台干净的机器、一个和代码绑定、事后能查的结论。你要的是这个,不是「省一次手动操作」。
那些「免费一推就上线」的平台,通常只允许个人、非商业用途。Vercel 官方在免费档的说明里写明该档仅限非商业的个人使用;GitHub Pages 也在自己的限制页明确表示不打算被当作免费虚拟主机,用来跑以促成商业交易为主的网站。这两句都不写在你的 YAML 里,而写在服务条款里——等你那个半成品项目真的开始收钱时,它们会是第一个出问题的地方,值得在那之前就换到对应的付费档或换平台。出处见第 15 节。
11六个最常见的误解
为什么这个误解诱人:因为在线效果最直观。你推一下,网页变了,这个反馈太强了,于是你以为这就是全部。
具体的失效形态:清单里只有一句
npm run build,那么「页面能构建出来」是绿了,但按钮点不点得动、付款逻辑对不对,它完全不知道。一句话判据:看到绿灯时问自己「它到底检查了哪几件事?」——答不出,这个绿对你没有意义。
为什么诱人:因为「自动」这个词暗示了无人值守。而现实是它把工作从「操作」变成了「看结果和定期核对规则」——量小了,但不能是零。
所以:一个连测试都没有的项目,照样能立刻从流水线里拿到好处,只是别骗自己「我有 CI 了所以很稳」。
但要注意反面:一个人也不该配一个大团队才维护得动的复杂流水线(多环境、多阶段审批)。够用就停。
你不需要会写,但必须会读——就是 §04 那五个问题。两分钟的事,一次就够。
12红灯先看这五处 / 绿灯可疑的四种情况
红灯:按这个顺序查,别从头读日志
| 症状 | 先看哪里 | 常见原因(按出现频率) |
|---|---|---|
| 推上去了,但 Actions 里根本没有这次运行 | 文件路径与 on: 那一段 |
路径不是逐字的 .github/workflows/文件名.yml;触发条件限定了别的分支;如果是靠手动按钮触发,那条配置必须已经存在于默认分支上,页面才会出现按钮 |
| 失败在「装依赖」那一步 | 那一步日志的末尾二十行 | 锁文件与运行时版本不匹配;某个依赖源临时不可用;缓存里存了坏东西(重跑一次往往就好) |
| 代码一个字没改,昨天还绿今天就红 | 平台有没有更新默认环境 | 两类外部变化:你引用的现成动作被升级了(所以版本号要写死),或者平台把默认机器换版本了——例如官方公告说 ubuntu-latest 将在 2026-10-19 起从 Ubuntu 24.04 迁移到 26.04,这类迁移会让一部分项目突然开始失败 |
| 报错写着权限不足 / 拒绝访问 | 配置里的 permissions 段落,以及仓库设置里的默认令牌权限 |
发布类动作需要显式授权;组织账号的项目往往还有一层仓库级默认设置 |
| 流水线全绿,但线上没变化 | 发布那一步的日志,以及部署目标的开关 | 托管页面的项目需要先在设置里把来源选成「GitHub Actions」;也可能发到了预览地址而不是正式地址;再或者是缓存/CDN 还没刷新 |
绿灯:四种「绿得不该信」的情况
被跳过和执行成功在页面上是两种不同的标记(灰色对绿色)。跳过通常来自 if 条件不成立——例如「只在 main 上跑测试」,而你这次推的是别的分支。整体结论可能仍然显示为「通过」。看到绿之前,先数一眼有没有灰的。
有些人(和 AI)为了让第一次跑通,先把命令换成一句恒真的输出,然后再也没换回来。判断法就是 §04 第四个问题:「检查到底是哪一行?」那一行读不出「什么情况下它会失败」,就不是检查。
比如只测了其中一个模块、只构建了其中一个页面、只在你最常用的那个系统上跑。它没撒谎,只是覆盖的比你以为的少。把清单和你的预期对一遍,这也是为什么要在笔记里留一份「它本来该干什么」。
页面默认显示的可能不是最近一次,或者你点开的是同名的另一条流水线。用编号对:确认这一次运行的编号,和你这次改动的时间对得上。这是最容易发生、也最容易纠正的一种误读。
13一页纸:以后回来只翻这一节
「我要做什么」→ 抄哪一段
| 我想…… | 去抄哪一段 | 抄完必须再做一件事 |
|---|---|---|
| 先看见一次绿灯,搞清机制 | §05 第一段那份 hello 配置 | 再故意弄红一次,看红灯长什么样 |
| 让机器每次改动都替我检查 | §05 第二段那份检查配置 | 设成「必须通过」(§06),否则它拦不住任何人 |
| 让静态站自动上线 | §05 第三段那份发布配置 | 在仓库设置里把页面来源选成 GitHub Actions |
| 让 AI 的活有个交付标准 | §08 那段任务卡 + 开场话术 | 把「最多修两轮」也写进去 |
| 让别人(或 AI)写的配置能被我看住 | §04 那五个问题 | 把答案存进自己的笔记,作为「它本来该干什么」的底本 |
YAML 字段速查
| 字段 | 一句话 | 最常见写法 |
|---|---|---|
name | 这份清单叫什么 | 任意中文或英文 |
on | 什么时候跑 | push / pull_request / workflow_dispatch / schedule |
jobs | 要干哪几件事 | 下面每项一个作业 |
runs-on | 用哪种机器 | ubuntu-latest |
steps | 这个作业里的步骤 | 按顺序执行 |
uses | 用哪段现成动作 | actions/checkout@v7(版本号必须自己核) |
with | 给它的参数 | node-version: 22 |
run | 直接跑哪条命令 | npm test(这一行就是「检查」本身) |
needs | 等谁成功 | 发布作业写它 |
if | 满足条件才跑 | 被跳过的头号原因 |
permissions | 给它多大权限 | 只给需要的(contents: read 等) |
页面上那几种标记
| 你看到 | 意思 | 你该做什么 |
|---|---|---|
| 黄点 / 转圈 | 正在跑或排队 | 等,或点进去看卡在哪一步 |
| 绿勾 | 这一步/这个作业成功 | 确认它真的执行了,不是被跳过 |
| 红叉 | 失败,后面的步骤默认不再执行 | 看第一个红的那一步,读日志末尾 |
| 灰色横杠 | 被跳过,不是成功 | 查 if 条件为什么没成立 |
命令速查(交给 AI 用,或你自己敲)
gh run list --limit 10 # 最近十次运行:哪次红、哪次绿、编号是多少
gh run view <编号> --log-failed # 只看失败那一步的日志(最有用的那一段)
gh run rerun <编号> --failed # 把失败的作业重跑一遍(怀疑是环境抽风时用)
gh pr checks # 看当前合并请求上各检查的状态
三段随时可抄的话
「不要把我的任何密钥写进文件;需要时用 Secrets 的名字引用,并告诉我该在哪个页面创建、叫什么。不要为了跑通把权限开到最大。」
「这个任务算完成,指的是我在 Actions 页面看到这次改动对应的运行是绿的,且里面的检查都真的跑了。改完先告诉我:第几次运行、哪一步、什么结论。」
「连续两次修不好就停下,告诉我你怀疑哪里错了,不要继续试。也不许为了让检查变绿而删测试、跳过测试或改松检查。」
14名词速查:别把它们当成什么用
留一张表在这里,是为了你以后在别处看到这些词时能快速归位——尤其是当 AI 或教程把这些词混着用的时候。
| 名词 | 人话解释 | 为什么重要 / 何时用 · 别当什么用 |
|---|---|---|
| CI(持续集成) | 每改一点就把改动合进主干,并且每次合都让机器自动验一遍 | 重点在「频繁合」,不在「自动跑」;别把它当成一个工具名,它是一套习惯加一套机制 |
| CD(持续交付 / 持续部署) | 交付=随时能发但人按;部署=自动发出去 | 这两个词差别巨大,听到「我们上了 CD」时值得追问一句是哪个;别把自动部署直接当成 CI/CD 的全部 |
| 流水线 / 工作流(pipeline / workflow) | 那份「每次要做什么」的明文清单,以及它执行一次的全过程 | 改行为就是改这份文件,所以它是审查重点;别以为它藏在某个平台的设置页里摸不到 |
触发(trigger / on:) |
什么事件会让清单开始执行 | 这是你读配置时第一个该看懂的地方;别把它当注释跳过——「该跑没跑」几乎都出在这里 |
| 作业(job) | 清单里一件相对独立的事,各自跑在一台机器上 | 作业之间默认不共享文件;它的名字会被「必须通过」的规则引用,改名会让规则静默失效 |
| 步骤(step) | 作业里的一行:跑一条命令,或用一段现成动作 | 失败时只看第一个红的那些行;别看整本日志 |
现成动作(action / uses:) |
别人写好、公开可复用的一小段自动化 | 它带版本号且会演进,是「AI 写错」和「安全漏洞」的高发区;别当成自己项目里的代码,它是外部依赖 |
| 执行器(runner) | 真正跑你清单的那台机器 | 平台给的是干净临时机器,所以「我本地是好的」不算证据;自托管 runner 更自由但要你自己维护安全 |
| 产物(artifact) | 运行过程中特意留下来、可下载的文件 | 它也是「构建」到「发布」之间的传送带;别指望不声明就能自动传递 |
| 状态检查(status check) | 「这个提交通过没通过」这条记录 | 它只是信息。只有设成「必须通过」才有否决权——这一步不做,前面全白干 |
| 必须通过(required status checks) | 一条规则:这项检查不绿,就不许合并 | 把 AI 的「自述」变成制度的关键一步;别在紧急情况下忘了自己有管理员通道 |
| YAML | 用缩进表达层级的纯文本配置格式 | 缩进是语法不是排版,且不能用 Tab;从别处复制配置后「无缘无故跑不起来」多半出在这 |
| 密钥(secrets) | 拿着它就能以你的身份做事的字符串 | 唯一一处「不看会出事」的地方(§07);它只能存在平台的保险箱里,用名字引用 |
| OIDC / 临时凭据 | 运行时不存长期密码,而是现场向云服务商换一张几分钟有效的工牌 | 部署到云平台时值得上;别把信任关系设得太宽(例如允许任何分支来换) |
| 分叉(fork) | 把别人的公开仓库复制一份到自己名下改 | 它决定了「陌生人的合并请求拿不到你的密钥」这条安全设计;别为了让外部的请求跑起来而关掉这层保护 |
| 合并请求(PR / pull request) | 「我想把这些改动合进你的代码」的一份正式请求 | 它是检查结果与讨论的落点,也是你不懂代码时最主要的看板;别把它当成只有程序员才用的形式 |
| 检查格式(lint) | 自动挑出格式问题和一类低级错误的工具 | 性价比很高,几乎不需要你先写东西;但它不验逻辑,别把它当成测试 |
| 构建(build) | 把源代码加工成能运行的成品 | 最小可用的第一条检查;别和「部署」混起来——构建失败与服务器无关 |
| 缓存(cache) | 把「装依赖」的结果存起来,下次直接用 | 它是买时间的,不是买正确的;缓存出问题时的典型表现是「重跑一次就好了」 |
| 权限(permissions) | 这段自动化被允许做什么 | 默认偏宽,是安全审查的第一处;原则是「这一步需要什么就给什么」 |
15来源与证据边界
这篇里凡是「现在是多少」「当前叫什么」的判断,都来自下面这些页面,核对日期统一为 2026-09-23。
官方文档(一手)
- docs.github.com — GitHub Actions 计费 — 公开仓库与自托管免费、各档私有仓库的分钟与存储额度(§02、§10)
- docs.github.com — 运行机器计价 — 超额单价与「更大机器永远收费」(§02、§10)
- docs.github.com — 工作流语法 —
on/jobs/needs/permissions/concurrency的权威定义(§03、§04、§06) - docs.github.com — 触发工作流的事件 — 「手动触发需要配置已在默认分支上」等细节(§12)
- docs.github.com — Actions 快速上手 — 官方最小示例(§05 版本对照表里的「配置文档示例」一列)
- docs.github.com — 依赖缓存 — 缓存 7 天未访问即删除、默认上限 10GB/仓库(§04、§13)
- docs.github.com — 重跑运行与作业 — 页面上「Re-run」与命令行的对应(§08、§13)
- docs.github.com — 使用密钥 — §07 引用那句官方原文的出处,也是配置入口
- docs.github.com — 安全使用 pull_request_target — 「最常被误用的触发器」与 pwn request(§07)
- docs.github.com — 安全使用参考 — 最小权限、掩码、防脚本注入(§04、§07)
- docs.github.com — OpenID Connect — 「向云厂商换短寿命访问令牌」的原理(§07)
- docs.github.com — rulesets 可用规则 与 受保护分支 — 「Require status checks to pass before merging」的准确措辞与入口(§06)
- docs.github.com — 评审 AI 生成的代码 — §08 那三种「为了变绿而动手脚」的官方提醒出处
平台变更公告(本篇里时效性最强的部分)
- github.blog changelog(2026-06-18) —
actions/checkoutv7 默认拒绝在危险触发下抓取外部分支代码(§05、§07) - github.blog changelog(2026-06-26) — 对不可信触发只发只读的缓存凭证(§07 的同类加固)
- github.blog changelog(2026-09-17) —
ubuntu-latest将于 2026-10-19 起迁移到 Ubuntu 26.04(§02、§12 那条「代码没改却突然红」) - github.blog changelog(2026-05-07) — 并发组可排队数量扩大(§04 的
concurrency) - github.blog changelog(2025-12-16) — 计费调整公告;页内同时保留「自托管将收费」的原始公告与「推迟」的更新(见下方矛盾点)
- github.blog changelog(2026-02-05) — 自托管 runner 最低版本强制要求(§02 提到自托管要多担一份维护)
官方动作仓库(版本号以这里为准)
- actions/checkout(当日最新 v7.0.1)· actions/setup-node(v7.0.0)· actions/cache(v6.1.0)· actions/upload-artifact(v7.0.1)· actions/download-artifact(v8.0.1)
- actions/configure-pages(v6.0.0)· actions/upload-pages-artifact(v5.0.0)· actions/deploy-pages(v5.0.1)
- actions/starter-workflows — 官方入门模板;本文用它作为「官方来源也会滞后」的实例(§05 那张表)
部署平台(「一推就上线」那一层)
- Vercel — 用 Git 仓库部署 — 官方原文:每次分支推送都会产生一次预览部署,生产分支上的改动产生生产部署(页面标注最后修改 2026-09-08)(§09)
- Vercel — Instant Rollback — 把生产环境快速回退到更早的一次生产部署(§09、§12)
- Cloudflare Pages — Git 集成 — 「把 GitHub / GitLab 仓库接上后,推送即自动构建并部署」(§09)
- Netlify — Deploy Previews — 每个合并请求生成一份可预览的部署(§09)
- docs.github.com — 配置 Pages 的发布来源 — Settings → Build and deployment → Source 的两个选项,以及官方推荐「写一个 Actions 工作流来发布」的原文(§05、§12)
- Vercel — 免费档(Hobby)说明 — 「该档仅限非商业、个人用途」的官方原文(§10)
- docs.github.com — GitHub Pages 使用限制 — 站点体积与带宽上限,以及「不打算被当作免费虚拟主机跑商业站点」的原文(§10)
AI 与流水线结合的部分
- Claude Code — GitHub Actions 文档(安装命令
/install-github-app、触发方式与密钥)· anthropics/claude-code-action · 其安全说明(触发者需写权限、默认拒绝机器人) - 安全公告 GHSA-8q5r-mmjf-575q(CVE-2026-47751,2026-06-10,修复于 1.0.74) — §08 那个真实事故的一手材料
- Codex 的 GitHub 集成(
@codex review、@codex fix the CI failures)· openai/codex-action · Codex 手册(套餐与计费口径) - Copilot 代码审查(默认只留「评论」型审查、不计入批准)· Copilot 云代理(指派 issue 开 PR、会话上限 59 分钟)
- Cursor Bugbot — 自动评审与手动触发(§08 表格里的一行)
- CSA Lab 研究笔记「Comment and Control」(2026-04-17) — 单一来源,见下方证据边界
- zizmor 的检查规则清单 — 一份现成的「流水线配置该被查哪些项」清单(未固定版本、权限过大、危险触发器等)。本文没有让你去装它,只把它的规则当成核对清单来用
时效:这篇属于「演进型」——机制与判断不容易过时,但版本号、免费额度、默认运行环境都在变。本文校对日期为 2026-09-23;所有额度、单价、版本号均为当日从上述官方页面抓取。用之前请以当期页面为准,尤其是钱的部分。
三处已知的矛盾,本文都保留了两边:
① 官方自己三处说法不一致(§05 那张表就是它):官方入门模板写 checkout@v4、官方快速上手文档写 checkout@v6、而该动作自己的 README 与发行页已是 v7;deploy-pages 的 README 示例还停在 v4,发行页已是 v5。本文取「发行页最新」作为可用版本,并把这张表本身当成教学材料。
② 自托管 runner 到底收不收费:同一篇公告页里,原始公告说 2026-03-01 起对自托管按分钟收费,而页面顶部的更新说这项收费「推迟、需重新评估」;现行计费文档仍写自托管免费。本文取「已推迟、当前免费」。
③ actions/cache 的 v5 是否已弃用:v6.1.0 与 v5.1.0 在同一天发布,页面把 v6 标为最新,但官方没有说 v5 弃用。本文只建议「跟最新主版本」,不宣称 v5 不可用。
三处未核实,本文不用它们当论据:
① 上面那份「Comment and Control」研究:它称涉及三家厂商的产品,但我没有找到这三家自己发布的公告或编号,因此本文把它标注为单一来源,只用来告诉你「这一类风险存在」,不当成已确认的厂商事实。
② 有第三方文章声称「AI 生成的流水线配置错误率更高,2024–2025 扫描可证」——我没有找到该研究的一手出处,因此本文不引用这个统计;§04 与 §08 的结论只基于可逐条核对的具体机制(版本号会过期、权限默认偏宽、触发条件危险)。
③ 有些 AI 编程工具(如 aider)在流水线里的官方做法,我没有找到权威说明,因此本文没有列它。
一处口径说明:文中所有「页面上的措辞」都来自英文界面(例如 Require status checks to pass before merging)。中文界面的翻译可能与此不同,看到意思一致、位置一致的项即可。
部署平台的额度数字,本文一个都没引用:这几家的免费额度改版频繁(Netlify 已从「带宽 + 构建分钟」换成信用额度制;Vercel 免费档的额度表里并没有单列「构建分钟」这一项),而旧文章、旧截图里流传的数字往往已经过期。所以第 09、10 节只写「能力」与「用途限制」这两个相对稳定的判断,具体额度请点上面两条链接看当期页面——这本身就是本文第 12 节那条「数字必须自己核」的一个现场示范。
本文自己做的核对(不是转述)
§05 那张「三个官方来源给出三套版本号」的表,是本文在 2026-09-23 当天逐个打开各动作仓库的发行页与 README、对照官方入门模板与官方快速上手文档后整理出来的。复核方式:打开对应仓库的 Releases 页面,看排在最上面的大版本号——这一步你自己五分钟就能重做一遍,而且建议你重做,因为它正是本文要教的那个动作。
99一句话总结
CI/CD 是把「每次改动都要做的事」写进一份明文清单,交给一台你控制不了的干净机器,在每次改动时自动执行,并把结果压成一个和这次改动绑定、事后可查的「通过 / 不通过」。
对不写代码、靠 AI 推进项目的你,它的价值不在省事,而在把 AI 那句「我改好了」换成一件你亲眼看得见的事实。为此你只需要三件事:读懂那份配置(五个问题)、把「必须通过」这个开关打开、以及守住「修 bug 不许改测试」这条纪律。其余的一切——版本号、权限、密钥、什么时候别上——本文都给了可以直接照做的判断法。