Authoring GitHub Actions Workflows Safely
GitHub Actions workflow files are YAML, but valid YAML is not the same as a valid workflow. A file can parse as YAML yet still be rejected by GitHub Actions before any job starts.
Repository integration:
AGENTS.mdand the existing Dapper-FluentMap workflows are authoritative. Preserve the repository's SHA-pinned actions, least-privilege permissions, release/recovery semantics,masterdefault branch, Sonar Quality Gate, and multi-package publishing rules. Useci-release-governancefor what a workflow should do; use this skill for whether the workflow is structurally valid and safely authored.
When to Use
- Editing, adding, or reviewing
.github/workflows/*.yml. - Writing
run-name,name,if,env,with, orrunvalues containing${{ }}expressions. - Diagnosing a workflow rejected before jobs start.
- Reviewing download steps, permissions, expressions, quoting, or reusable workflow syntax.
The # trap
In an unquoted YAML scalar, a space followed by # begins a comment and can truncate a GitHub expression.
# BAD
run-name: ${{ inputs.pr_number != '' && format('Evaluate PR #{0}', inputs.pr_number) || '' }}
# GOOD
run-name: "${{ inputs.pr_number != '' && format('Evaluate PR #{0}', inputs.pr_number) || '' }}"
Quote expression-bearing scalars whenever literal #, colon-space, leading special characters, or significant whitespace make YAML interpretation ambiguous.
Workflow
1. Identify changed workflows
git diff --name-only origin/master... -- .github/workflows/
2. Inspect repository semantics before editing
For release-related changes, read the existing release.yml, recovery workflow, eng/package-catalog.json, and relevant publishing scripts. Do not replace established behavior with a generic workflow pattern.
3. Validate with actionlint
Prefer a repository-owned pinned validation command when available. For manual validation:
ACTIONLINT_VERSION=1.7.12
ACTIONLINT_SHA256=8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8
curl \
--fail \
--silent \
--show-error \
--location \
--proto '=https' \
--proto-redir '=https' \
--output actionlint.tar.gz \
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
echo "${ACTIONLINT_SHA256} actionlint.tar.gz" | sha256sum -c -
tar -xzf actionlint.tar.gz actionlint
./actionlint -shellcheck= -pyflakes= -color .github/workflows/*.yml
Pin both version and checksum. Restrict both initial and redirected downloads to HTTPS.
The repository also uses GitHub-workflow JSON schema validation; preserve that existing gate rather than substituting actionlint for it.
4. Review security-sensitive workflow behavior
- keep actions SHA-pinned;
- keep permissions minimal and job-scoped when practical;
- do not expose secrets to fork PRs;
- do not add
continue-on-errorto bypass required quality/release checks; - do not weaken NuGet OIDC, artifact validation, release recovery, or Sonar Quality Gate behavior;
- for network downloads that follow redirects, enforce HTTPS redirects and validate checksums where feasible.
Validation
- Changed workflow files pass repository schema validation
-
actionlintexits 0 when used - Risky
${{ }}scalars are quoted - Actions remain SHA-pinned
- Permissions remain least privilege
- Redirect-following downloads enforce HTTPS
- Existing release, recovery, CI Gate, and Sonar semantics remain intact unless intentionally changed