3. 编写第一个工作流

前两节我们已经理解了 GitHub Actions 的组成要素:工作流(Workflow)、事件(Event)、作业(Job)、步骤(Step)和运行器(Runner)。这一节要把这些概念真正落到一个可运行的文件上——在仓库里创建 .github/workflows/ 目录,写下一个 YAML 文件,配置 on 触发器、jobs 与 steps,让它完成代码检出、打印环境信息,并在 GitHub 仓库中成功跑起来。这是从"看懂概念"到"跑通第一条流水线"的关键一步。

一、目录与文件命名规则

GitHub 只会在仓库根目录下的 .github/workflows/ 路径中查找工作流文件。这个路径是固定的,不能改名,也不能放到子目录里。目录本身以点开头,在 macOS/Linux 的终端里属于隐藏目录,用 ls 看不到,需要 ls -a 或直接在编辑器里展开。

文件名可以自由命名,但必须以 .yml 或 .yaml 结尾。建议用能表达用途的名字,例如 hello-workflow.yml、ci.yml。一个仓库里可以有多个工作流文件,GitHub 会把它们全部加载,各自独立触发。创建方式有两种:在本地仓库用命令行创建后 push,或者直接在 GitHub 网页上点击 Add file → Create new file,在文件名输入框里输入 .github/workflows/hello-workflow.yml,斜杠会自动帮你建出目录。

# 本地创建方式
mkdir -p .github/workflows
touch .github/workflows/hello-workflow.yml
git add .github/workflows/hello-workflow.yml
git commit -m "ci: add first workflow"
git push origin main

二、YAML 骨架:name、on、jobs

一个最小可运行的工作流由三块构成:name(可选,显示在 Actions 页面左侧)、on(触发器,必填)、jobs(作业集合,必填)。YAML 用缩进表达层级,只能用空格,不能用 Tab,这是新手最常见的报错来源。同一层级缩进必须一致,通常用 2 个空格。

name: Hello Workflow

on:
  push:
    branches: [ main ]
  workflow_dispatch:

jobs:
  greet:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Print environment info
        run: |
          echo "Repository: $GITHUB_REPOSITORY"
          echo "Triggered by: $GITHUB_ACTOR"
          echo "Commit SHA: $GITHUB_SHA"
          echo "Runner OS: $RUNNER_OS"
          uname -a
          node --version || true
          python3 --version || true

逐段看:on.push.branches: [ main ] 表示只有推送到 main 分支时才触发;workflow_dispatch 是手动触发开关,加上它之后,Actions 页面会出现一个 "Run workflow" 按钮,方便调试时不用反复提交代码。jobs.greet 是作业 ID,可以自定义,字母数字和短横线都行,它同时也是 Actions 页面里显示的名字来源之一。runs-on: ubuntu-latest 指定运行器镜像,GitHub 会分配一台全新的 Ubuntu 虚拟机来执行这个作业。

三、steps:检出与打印环境信息

steps 是一个有序列表,按顺序执行,任何一步失败(退出码非 0)后续步骤默认不再执行,整个作业标记为失败。每个 step 有两种写法:用 uses 调用现成的 Action,或用 run 执行 shell 命令。

第一步 uses: actions/checkout@v4 是几乎所有工作流的起点。运行器刚启动时是一个空目录,里面没有你的代码,checkout 这个官方 Action 会把当前触发提交的仓库内容克隆到工作目录 $GITHUB_WORKSPACE 下。没有这一步,后面的构建、测试都无从谈起。注意 @v4 是版本引用,锁定主版本可以避免上游破坏性变更,同时又能拿到该版本内的补丁更新。

第二步用 run 执行多行 shell。这里用竖线 | 表示保留换行的多行字符串,每一行都会在同一个 shell 会话里顺序执行。我们打印了几个 GitHub 内置的环境变量:GITHUB_REPOSITORY 是 owner/repo 形式,GITHUB_ACTOR 是触发者用户名,GITHUB_SHA 是本次提交的完整哈希,RUNNER_OS 是运行器系统。再配合 uname -a 和 node --version,就能确认运行器的内核版本和预装工具链。末尾的 || true 是容错写法:如果某个命令不存在,返回非 0 会导致步骤失败,加上它可以让这一步即使命令缺失也继续通过,适合做"探测性"输出。

第一个工作流的触发与执行链路 push 到 main 或手动 Run workflow on 触发器 branches: [main] .github/workflows/ hello-workflow.yml name: Hello Workflow on: push / dispatch jobs: greet runs-on: ubuntu-latest steps: [checkout, run] Runner: ubuntu-latest 虚拟机 Step 1 · actions/checkout@v4 克隆仓库到 $GITHUB_WORKSPACE Step 2 · run: 打印环境信息 GITHUB_REPOSITORY / ACTOR / SHA uname -a · node --version Actions 页面结果判读 绿色对勾:作业成功,所有 step 退出码为 0 红色叉号:某 step 失败,展开日志定位报错行 黄色圆点:YAML 语法错误或工作流未匹配触发条件

四、提交并观察运行结果

文件保存后提交并推送到 main 分支,触发条件立即生效。打开仓库页面顶部的 Actions 标签,左侧会看到 "Hello Workflow" 这个名字,点进去能看到本次运行记录,标题就是提交信息。点开这次运行,右侧是 greet 作业,展开后可以看到两个 step 的实时日志。

判断是否成功,看三个信号:作业名左侧出现绿色对勾,说明所有 step 退出码为 0;点开 "Print environment info" 步骤,日志里应该能看到类似下面的输出:

Repository: yourname/yourrepo
Triggered by: yourname
Commit SHA: 3f9a1c2e8b7d4a5f6c0e1d2b3a4f5e6d7c8b9a0f
Runner OS: Linux
Linux runner-xyz 6.5.0-1021-azure #22-Ubuntu SMP ... x86_64 GNU/Linux
v20.11.1
Python 3.12.3

如果看到红色叉号,展开失败的 step 日志,最常见的三类错误是:YAML 缩进用了 Tab 或层级错位,报错信息通常是 "Invalid workflow file";uses 引用的 Action 版本不存在,例如写成 actions/checkout@v5;run 里的命令返回非 0,日志会直接显示该命令的 stderr。如果 Actions 页面里根本找不到这个工作流,多半是文件路径不对——检查是不是漏了开头的点,或者放到了 .github/workflow/(少了个 s)。

调试技巧:加上 workflow_dispatch 后,可以在 Actions 页面手动点 "Run workflow" 反复触发,不必每次都改代码提交。修改 YAML 后重新 push,新版本会立即生效,旧运行记录仍保留在列表里。

五、常见坑与检查清单

  • 缩进:统一用 2 个空格,禁止 Tab。编辑器建议开启 "Render Whitespace" 便于发现混用。
  • 冒号后空格:name: Hello 冒号后必须有空格,写成 name:Hello 会被解析成字符串而非键值对。
  • 分支名:branches: [ main ] 里的 main 要与仓库默认分支一致,老仓库可能是 master。
  • 权限:如果仓库开启了分支保护或 Actions 被组织策略禁用,工作流不会运行,需要在 Settings → Actions 中确认已允许。
  • 版本锁定:uses 建议写主版本号(如 @v4),不要用 @main,避免上游改动导致你的流水线突然失败。