authoring-github-workflows

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Authoring GitHub Actions Workflows Safely

安全编写GitHub Actions工作流

GitHub Actions workflow files are YAML, but valid YAML is not the same as a valid workflow. A workflow can parse cleanly with
yaml.safe_load
(or a casual review) yet still be rejected by GitHub Actions at load time — producing the opaque failure "This run likely failed because of a workflow file issue" with zero jobs started. This skill teaches the YAML-vs-Actions traps (the
#
-as-comment trap above all), how to quote expression scalars correctly, and how to validate with
actionlint
before merge.
Scope: syntactic vs. semantic. This skill is about the syntactic and structural correctness of workflow YAML — quoting, parsing, and
actionlint
-level validity that determines whether GitHub Actions will load and run a file at all. It is not about what a workflow should do or how an agentic workflow should behave. For semantic and functional guidance (designing workflow logic, agentic-workflow patterns, gh-aw authoring), use
.github/agents/agentic-workflows.agent.md
. The two are complementary: get the behavior right with the agent, get the YAML right with this skill.
GitHub Actions工作流文件采用YAML格式,但有效的YAML不等于有效的工作流。某个工作流可能通过
yaml.safe_load
解析(或通过常规审核),但在加载时仍会被GitHub Actions拒绝——显示模糊的失败提示*"此运行可能因工作流文件问题而失败"*,且无任何作业启动。本技能将讲解YAML与Actions之间的陷阱(尤其是
#
作为注释的陷阱)、如何正确为表达式标量添加引号,以及如何在合并前使用
actionlint
进行验证。
范围:语法 vs 语义。本技能聚焦工作流YAML的语法和结构正确性——引号使用、解析以及
actionlint
层面的有效性,这些决定了GitHub Actions是否会加载并运行文件。它涉及工作流应实现的功能或智能工作流的行为逻辑。若需语义和功能指导(工作流逻辑设计、智能工作流模式、gh-aw编写),请使用
.github/agents/agentic-workflows.agent.md
。两者相辅相成:借助智能代理实现正确行为,借助本技能确保YAML格式正确。

When to Use

适用场景

  • Editing, adding, or reviewing any file under
    .github/workflows/
    .
  • Writing a
    run-name
    ,
    name
    ,
    if
    ,
    env
    ,
    with
    , or
    run
    value that embeds a
    ${{ }}
    expression.
  • A workflow run failed with "This run likely failed because of a workflow file issue" and no jobs ran.
  • Eval/CI on
    main
    suddenly breaks for every run after a workflow edit merged, even though the change "looked fine."
  • Deciding whether a YAML scalar needs quoting.
  • 编辑、添加或审核
    .github/workflows/
    目录下的任何文件。
  • 编写嵌入
    ${{ }}
    表达式的
    run-name
    name
    if
    env
    with
    run
    值。
  • 工作流运行失败,提示*"此运行可能因工作流文件问题而失败"*且无作业执行
  • 合并工作流编辑后,
    main
    分支的Eval/CI突然每次运行都失败,尽管更改看起来"没问题"。
  • 判断YAML标量是否需要加引号。

When Not to Use

不适用场景

  • Authoring non-Actions YAML (app config, Kubernetes, Compose, Azure Pipelines, GitLab CI).
  • Pure shell/script logic inside an already-valid
    run:
    block (that is a scripting task, not a workflow-syntax task).
  • 编写非Actions相关的YAML(应用配置、Kubernetes、Compose、Azure Pipelines、GitLab CI)。
  • 已验证有效的
    run:
    块内的纯Shell/脚本逻辑(这属于脚本任务,而非工作流语法任务)。

The #1 Trap:
#
inside an unquoted expression becomes a YAML comment

头号陷阱:未加引号的表达式中的
#
会成为YAML注释

In YAML, a space followed by
#
starts a comment. In an unquoted (plain) scalar, everything from that space-then-
#
to end-of-line is silently discarded:
yaml
undefined
在YAML中,空格后跟
#
会开始一段注释。在未加引号的(普通)标量中,从空格加
#
到行尾的所有内容会被静默丢弃:
yaml
undefined

BAD — the run-name is silently truncated at " #"

错误示例 —— run-name会在" #"处被静默截断

run-name: ${{ inputs.pr_number != '' && format('Evaluate PR #{0} @ {1}', inputs.pr_number, inputs.head_sha) || '' }}

YAML parses this as `run-name: ${{ inputs.pr_number != '' && format('Evaluate PR` — an **unterminated `${{` expression**. `yaml.safe_load` succeeds (it just sees a truncated string with a trailing comment), so the bug passes naive validation, but GitHub Actions rejects the malformed expression and refuses to start any run.

```yaml
run-name: ${{ inputs.pr_number != '' && format('Evaluate PR #{0} @ {1}', inputs.pr_number, inputs.head_sha) || '' }}

YAML会将其解析为`run-name: ${{ inputs.pr_number != '' && format('Evaluate PR` —— 一个**未终止的`${{`表达式**。`yaml.safe_load`会成功解析(它只会看到一个带尾随注释的截断字符串),因此该bug会通过简单验证,但GitHub Actions会拒绝这个格式错误的表达式,且不会启动任何运行。

```yaml

GOOD — wrap the whole value in double quotes so '#' stays inside the scalar

正确示例 —— 将整个值用双引号包裹,使'#'保留在标量内

run-name: "${{ inputs.pr_number != '' && format('Evaluate PR #{0} @ {1}', inputs.pr_number, inputs.head_sha) || '' }}"

The inner expression already uses single quotes, so double-quoting the scalar is safe. This is exactly the bug that broke `dotnet/skills` evaluation on `main` (PR #746 → fixed by quoting).
run-name: "${{ inputs.pr_number != '' && format('Evaluate PR #{0} @ {1}', inputs.pr_number, inputs.head_sha) || '' }}"

内部表达式已使用单引号,因此用双引号包裹标量是安全的。这正是导致`dotnet/skills`在`main`分支上的评估失败的bug(PR #746 → 通过添加引号修复)。

Other characters that force quoting in a plain scalar

其他会强制普通标量加引号的字符

Character / patternWhy it breaksFix
space then
#
(space-hash)
Starts a YAML comment; truncates the valueQuote the whole value
Leading
*
,
&
,
!
,
?
,
|
,
>
,
@
,
`
YAML anchors/aliases/tags/block scalarsQuote the value
Leading
{
or
[
Parsed as flow mapping/sequence (a bare
${{ }}
starts with
$
, which is safe, but
{{
after a leading char is risky)
Quote the value
:
then space (colon-space) inside the value
Parsed as a nested mapping keyQuote the value
Leading/trailing spaces that matterPlain scalars strip themQuote the value
Values that are
true
/
false
/
yes
/
no
/
on
/
off
/numbers but must stay strings
YAML type coercionQuote the value
Rule of thumb: if a
name
,
run-name
,
if
,
env
, or
with
value contains a
${{ }}
expression and any literal
#
,
:
, or leading special character, wrap the entire scalar in double quotes.
字符/模式导致问题的原因修复方案
空格加
#
(space-hash)
启动YAML注释;截断值为整个值添加引号
*
&
!
?
|
>
@
`
开头
YAML锚点/别名/标签/块标量为值添加引号
{
[
开头
被解析为流映射/序列(裸
${{ }}
$
开头是安全的,但开头字符后紧跟
{{
存在风险)
为值添加引号
值内包含
: 
(冒号加空格)
被解析为嵌套映射键为值添加引号
首尾空格有实际意义普通标量会自动去除首尾空格为值添加引号
值为
true
/
false
/
yes
/
no
/
on
/
off
或数字,但需要保持字符串类型
YAML类型转换为值添加引号
经验法则:如果
name
run-name
if
env
with
的值包含
${{ }}
表达式包含文字
#
:
或特殊开头字符,请将整个标量用双引号包裹

Workflow

操作流程

Step 1: Identify the changed/authored workflow files

步骤1:识别已更改/编写的工作流文件

bash
git diff --name-only origin/main... -- .github/workflows/
For each file, scan every line that contains
${{
together with a
#
, a colon-space, or a leading special character.
bash
git diff --name-only origin/main... -- .github/workflows/
对于每个文件,扫描所有包含
${{ }}
且同时包含
#
、冒号加空格或特殊开头字符的行。

Step 2: Quote risky expression scalars

步骤2:为存在风险的表达式标量添加引号

Wrap the full value in double quotes when the value embeds an expression and contains a
#
or other special character (see the table above). Prefer double quotes when the inner expression uses single quotes, and vice-versa. Do not escape the
${{ }}
braces — quoting the scalar is enough.
当值嵌入表达式且包含
#
或其他特殊字符时(见上表),将完整值用双引号包裹。如果内部表达式使用单引号,优先使用双引号作为外层包裹,反之亦然。不要转义
${{ }}
括号——为标量添加引号即可。

Step 3: Validate with actionlint (authoritative)

步骤3:使用actionlint验证(权威工具)

actionlint
understands the GitHub Actions schema and the expression grammar, so it catches exactly this class of bug that plain YAML linters miss. Download a pinned release and run it:
bash
ACTIONLINT_VERSION=1.7.7
ACTIONLINT_SHA256=023070a287cd8cccd71515fedc843f1985bf96c436b7effaecce67290e7e0757
curl -fsSLo actionlint.tar.gz \
  "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
actionlint
了解GitHub Actions的模式以及表达式语法,因此它能准确捕捉普通YAML检查工具遗漏的此类bug。下载固定版本并运行:
bash
ACTIONLINT_VERSION=1.7.7
ACTIONLINT_SHA256=023070a287cd8cccd71515fedc843f1985bf96c436b7effaecce67290e7e0757
curl -fsSLo actionlint.tar.gz \
  "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"

Verify the download against the pinned checksum before extracting/executing it:

在解压/执行前,对照固定校验和验证下载文件:

echo "${ACTIONLINT_SHA256} actionlint.tar.gz" | sha256sum -c - tar -xzf actionlint.tar.gz actionlint
echo "${ACTIONLINT_SHA256} actionlint.tar.gz" | sha256sum -c - tar -xzf actionlint.tar.gz actionlint

Focus on workflow/expression correctness; silence shell/py style noise:

聚焦工作流/表达式正确性;屏蔽Shell/Python风格的无关警告:

./actionlint -shellcheck= -pyflakes= -color .github/workflows/*.yml

On Windows PowerShell, use the `actionlint_<ver>_windows_amd64.zip` asset and `Expand-Archive`.

The truncated-expression bug surfaces as:
got unexpected EOF while lexing end of string literal, expecting ''' [expression]

A clean exit code `0` means the workflows are structurally valid.
./actionlint -shellcheck= -pyflakes= -color .github/workflows/*.yml

在Windows PowerShell中,使用`actionlint_<ver>_windows_amd64.zip`资源包和`Expand-Archive`命令。

截断表达式bug会显示为:
got unexpected EOF while lexing end of string literal, expecting ''' [expression]

返回干净的退出码`0`表示工作流结构有效。

Step 4: Confirm a YAML-only check is not enough

步骤4:确认仅YAML检查并不足够

Do not rely on
yaml.safe_load
,
yamllint
, or "it parses" as proof. They accept the truncated-comment form. Only
actionlint
(or pushing and watching GitHub Actions parse it) validates the Actions layer.
不要依赖
yaml.safe_load
yamllint
或"能解析"作为验证依据。它们会接受带截断注释的格式。只有
actionlint
(或推送后观察GitHub Actions的解析情况)能验证Actions层面的有效性。

Step 5: Keep the CI gate green

步骤5:保持CI检查通过

This repository runs
actionlint
automatically (see
.github/workflows/actionlint.yml
) on any PR that touches
.github/workflows/
. Ensure your change passes that check before requesting review. If you add a new workflow, the gate covers it automatically.
本仓库会自动对任何修改
.github/workflows/
的PR运行
actionlint
(见
.github/workflows/actionlint.yml
)。在请求审核前,确保你的更改通过该检查。如果添加新工作流,该检查会自动覆盖它。

Validation

验证清单

  • Every
    ${{ }}
    value containing
    #
    , a colon-space, or a leading special character is wrapped in quotes.
  • actionlint -shellcheck= -pyflakes= .github/workflows/*.yml
    exits
    0
    .
  • No workflow run reports "This run likely failed because of a workflow file issue".
  • The
    actionlint
    CI check is green on the PR.
  • 所有包含
    #
    、冒号加空格或特殊开头字符的
    ${{ }}
    值都已添加引号。
  • actionlint -shellcheck= -pyflakes= .github/workflows/*.yml
    返回退出码
    0
  • 无工作流运行报告*"此运行可能因工作流文件问题而失败"*。
  • PR上的actionlint CI检查显示通过。

Common Pitfalls

常见陷阱

PitfallSolution
Unquoted
run-name
/
name
with
#
inside the expression
Wrap the whole value in double quotes
Trusting
yaml.safe_load
/
yamllint
/a code review to catch it
Run
actionlint
; YAML-only checks accept the truncated form
Escaping
${{
braces to "fix" it
Don't — quote the scalar instead; escaping breaks the expression
Using single quotes around a value that contains single quotesUse double quotes for the outer scalar
Adding
actionlint
with shellcheck enabled and drowning in pre-existing shell-style warnings
Run with
-shellcheck= -pyflakes=
to focus on workflow/expression errors
Assuming a green YAML lint means the workflow will runPush and confirm jobs actually start, or rely on the actionlint gate
陷阱解决方案
未加引号的
run-name
/
name
,其表达式内包含
#
将整个值用双引号包裹
依赖
yaml.safe_load
/
yamllint
/代码审核来捕捉问题
运行
actionlint
;仅YAML检查会接受截断格式
通过转义
${{
括号来"修复"问题
不要这么做——改为为标量添加引号;转义会破坏表达式
用单引号包裹包含单引号的值为外层标量使用双引号
启用shellcheck的情况下添加actionlint,被大量现有Shell风格警告淹没使用
-shellcheck= -pyflakes=
参数运行,聚焦工作流/表达式错误
认为YAML检查通过就意味着工作流能正常运行推送并确认作业实际启动,或依赖actionlint检查

References

参考资料