@htekdev/actions-debugger 1.0.48 → 1.0.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/errors/caching-artifacts/caching-artifacts-035.yml +119 -0
- package/errors/permissions-auth/permissions-auth-037.yml +100 -0
- package/errors/runner-environment/runner-environment-104.yml +103 -0
- package/errors/runner-environment/runner-environment-105.yml +111 -0
- package/errors/silent-failures/silent-failures-052.yml +102 -0
- package/errors/triggers/triggers-037.yml +79 -0
- package/errors/triggers/triggers-038.yml +126 -0
- package/errors/yaml-syntax/yaml-syntax-036.yml +100 -0
- package/package.json +1 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
id: caching-artifacts-035
|
|
2
|
+
title: 'actions/cache fail-on-cache-miss: true causes first-run workflow failure — no cache exists on initial or rekeyed run'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- fail-on-cache-miss
|
|
8
|
+
- first-run
|
|
9
|
+
- actions-cache
|
|
10
|
+
- cache-miss
|
|
11
|
+
- bootstrap
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'fail-on-cache-miss:\s*true'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'Error: Cannot find a cache that matches the specified keys'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- 'Error: Cannot find a cache that matches the specified keys'
|
|
19
|
+
- 'Cache not found for input keys: ...'
|
|
20
|
+
- '##[error]Cannot find a cache that matches the specified keys'
|
|
21
|
+
root_cause: |
|
|
22
|
+
The actions/cache action added a fail-on-cache-miss input (introduced in v3.3.0,
|
|
23
|
+
available in v4). When set to true, the action exits with a non-zero status code
|
|
24
|
+
if no cache entry matches the provided key or restore-keys list. The intended use
|
|
25
|
+
case is detecting stale or misconfigured cache keys in established pipelines.
|
|
26
|
+
|
|
27
|
+
The critical problem: on the very first run of a workflow (new repository, newly
|
|
28
|
+
added cache step, or after changing the cache key expression), NO cache exists by
|
|
29
|
+
definition. With fail-on-cache-miss: true, the action fails immediately and the
|
|
30
|
+
job exits before any steps produce the artifacts that would be cached.
|
|
31
|
+
|
|
32
|
+
This creates a bootstrap deadlock:
|
|
33
|
+
1. Job starts — no cache exists — fail-on-cache-miss: true fires — job fails
|
|
34
|
+
2. Post-step cache save never runs because the job failed
|
|
35
|
+
3. Next run repeats step 1 — cache is never populated — workflow is permanently broken
|
|
36
|
+
|
|
37
|
+
The same issue occurs whenever the cache key expression changes (adding runner OS,
|
|
38
|
+
changing the hash source file, bumping a version prefix), because all existing
|
|
39
|
+
caches miss the new key pattern. Until the new cache is warm, every run fails.
|
|
40
|
+
|
|
41
|
+
Matrix builds compound the problem: each unique matrix dimension (OS × version)
|
|
42
|
+
needs its own initial run to seed the cache, so all combinations fail in parallel
|
|
43
|
+
on first use of the new key.
|
|
44
|
+
fix: |
|
|
45
|
+
Remove fail-on-cache-miss: true from the actions/cache step unless you have an
|
|
46
|
+
externally pre-seeded cache and a specific requirement to guarantee it exists
|
|
47
|
+
(rare, advanced use case).
|
|
48
|
+
|
|
49
|
+
For the common use case of skipping expensive install steps on cache hit:
|
|
50
|
+
use the cache-hit output instead. cache-hit is 'true' on exact key match and
|
|
51
|
+
allows downstream steps to be conditional, while still letting the job complete
|
|
52
|
+
successfully on a miss so the cache can be populated.
|
|
53
|
+
|
|
54
|
+
If you genuinely need to gate on cache existence (e.g., a nightly build that
|
|
55
|
+
consumes a separately-seeded model or dataset cache), run a dedicated cache-warming
|
|
56
|
+
workflow first and use fail-on-cache-miss: true only after confirming the seeder
|
|
57
|
+
workflow has run at least once.
|
|
58
|
+
fix_code:
|
|
59
|
+
- language: yaml
|
|
60
|
+
label: 'Use cache-hit output for conditional install instead of fail-on-cache-miss'
|
|
61
|
+
code: |
|
|
62
|
+
jobs:
|
|
63
|
+
build:
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
steps:
|
|
66
|
+
- uses: actions/checkout@v4
|
|
67
|
+
|
|
68
|
+
- name: Cache npm dependencies
|
|
69
|
+
id: cache-npm
|
|
70
|
+
uses: actions/cache@v4
|
|
71
|
+
with:
|
|
72
|
+
path: ~/.npm
|
|
73
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
74
|
+
restore-keys: |
|
|
75
|
+
${{ runner.os }}-npm-
|
|
76
|
+
# AVOID: fail-on-cache-miss: true ← breaks first run (deadlock)
|
|
77
|
+
|
|
78
|
+
- name: Install dependencies
|
|
79
|
+
# Only runs on cache miss — fast path skipped on hit
|
|
80
|
+
if: steps.cache-npm.outputs.cache-hit != 'true'
|
|
81
|
+
run: npm ci
|
|
82
|
+
|
|
83
|
+
- name: Build
|
|
84
|
+
run: npm run build
|
|
85
|
+
|
|
86
|
+
- language: yaml
|
|
87
|
+
label: 'Dedicated cache-warming workflow to pre-seed before fail-on-cache-miss use'
|
|
88
|
+
code: |
|
|
89
|
+
# .github/workflows/warm-cache.yml — run this first to pre-seed
|
|
90
|
+
name: Warm Cache
|
|
91
|
+
on:
|
|
92
|
+
schedule:
|
|
93
|
+
- cron: '0 6 * * 1'
|
|
94
|
+
workflow_dispatch: # Allow manual trigger when rekeying cache
|
|
95
|
+
|
|
96
|
+
jobs:
|
|
97
|
+
warm:
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/checkout@v4
|
|
101
|
+
- name: Cache dependencies
|
|
102
|
+
uses: actions/cache@v4
|
|
103
|
+
with:
|
|
104
|
+
path: ~/.npm
|
|
105
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
106
|
+
- name: Install to populate cache
|
|
107
|
+
run: npm ci
|
|
108
|
+
prevention:
|
|
109
|
+
- 'Never set fail-on-cache-miss: true on a fresh workflow or after changing the cache key expression — the cache cannot exist yet'
|
|
110
|
+
- 'Use cache-hit output and conditional if: steps.id.outputs.cache-hit != true for skip-on-hit behavior without failing on miss'
|
|
111
|
+
- 'When rekeying cache (OS prefix, hash source change), trigger a manual cache-warming workflow_dispatch run before deploying the new key'
|
|
112
|
+
- 'Test new cache key expressions in a feature branch where a job failure will not block main'
|
|
113
|
+
docs:
|
|
114
|
+
- url: 'https://github.com/actions/cache'
|
|
115
|
+
label: 'actions/cache README — fail-on-cache-miss input documentation'
|
|
116
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows'
|
|
117
|
+
label: 'GitHub Docs: Caching dependencies to speed up workflows'
|
|
118
|
+
- url: 'https://github.com/actions/cache/blob/main/tips-and-workarounds.md'
|
|
119
|
+
label: 'actions/cache tips and workarounds — cache miss handling patterns'
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
id: permissions-auth-037
|
|
2
|
+
title: 'Environment secrets are only accessible to jobs that declare a matching environment: key — other jobs silently receive empty string'
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- environment
|
|
7
|
+
- secrets
|
|
8
|
+
- environment-secrets
|
|
9
|
+
- deployment-environment
|
|
10
|
+
- job-scoped
|
|
11
|
+
- secret-scope
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'environment:\s*[a-z0-9_-]+.*secrets\.|secrets\.[A-Z_]+'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "(No error — secrets.<SECRET_NAME> resolves to '' in jobs that do not declare the matching environment:)"
|
|
17
|
+
root_cause: |
|
|
18
|
+
GitHub Actions supports three secret scopes with different visibility:
|
|
19
|
+
1. Repository secrets — available to all jobs in all workflows in the repository
|
|
20
|
+
2. Organization secrets — available to authorized repositories/workflows
|
|
21
|
+
3. Environment secrets — ONLY available to jobs that declare environment: <env-name>
|
|
22
|
+
|
|
23
|
+
Environment secrets are scoped to a specific deployment environment and are
|
|
24
|
+
intentionally isolated. A job that references secrets.MY_ENV_SECRET without
|
|
25
|
+
declaring environment: production receives '' (empty string) for that secret
|
|
26
|
+
with no error, no warning, and no indication that the secret exists elsewhere.
|
|
27
|
+
|
|
28
|
+
This isolation is a security feature: environment secrets are only released to
|
|
29
|
+
jobs that have satisfied environment protection rules (required reviewers, wait
|
|
30
|
+
timers, deployment branch policies). However it becomes a silent failure when:
|
|
31
|
+
|
|
32
|
+
- A secret is accidentally created in an environment instead of the repository scope
|
|
33
|
+
- A reusable workflow job uses the secret but the caller job did not declare environment:
|
|
34
|
+
- A job is refactored to remove environment: (to skip protection rules in testing) but
|
|
35
|
+
still references the now-inaccessible environment secret
|
|
36
|
+
- A developer expects an environment secret to work like a repository secret
|
|
37
|
+
|
|
38
|
+
The effect is indistinguishable from the secret not existing: the value is '' and the
|
|
39
|
+
job may fail with an auth error, a blank config value, or silently produce wrong output.
|
|
40
|
+
fix: |
|
|
41
|
+
Determine the intended scope:
|
|
42
|
+
- If the secret should be accessible to all jobs: create it as a repository secret
|
|
43
|
+
(Settings > Secrets and variables > Actions > Repository secrets)
|
|
44
|
+
- If the secret must be gated by deployment protection rules: keep it as an environment
|
|
45
|
+
secret AND add environment: <env-name> to every job that needs it
|
|
46
|
+
- If both a repository secret and an environment secret share the same name:
|
|
47
|
+
the environment secret takes precedence in jobs that declare that environment
|
|
48
|
+
|
|
49
|
+
Use the GitHub Settings UI to confirm which scope a secret belongs to before
|
|
50
|
+
debugging unexpected empty values in run steps.
|
|
51
|
+
fix_code:
|
|
52
|
+
- language: yaml
|
|
53
|
+
label: 'Add environment: to the job that needs the environment-scoped secret'
|
|
54
|
+
code: |
|
|
55
|
+
jobs:
|
|
56
|
+
deploy:
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
# Without this environment: key, secrets.DEPLOY_API_KEY is ''
|
|
59
|
+
# even though it exists as an environment secret for 'production'
|
|
60
|
+
environment: production
|
|
61
|
+
steps:
|
|
62
|
+
- name: Deploy to production
|
|
63
|
+
env:
|
|
64
|
+
API_KEY: ${{ secrets.DEPLOY_API_KEY }}
|
|
65
|
+
run: echo "Deploying with scoped key"
|
|
66
|
+
|
|
67
|
+
- language: yaml
|
|
68
|
+
label: 'Separate build (no environment) from deploy (with environment) to scope protection rules to deploy only'
|
|
69
|
+
code: |
|
|
70
|
+
jobs:
|
|
71
|
+
build:
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
# No environment: here — only repository-scoped secrets are needed for build
|
|
74
|
+
steps:
|
|
75
|
+
- uses: actions/checkout@v4
|
|
76
|
+
- run: echo "Building artifacts"
|
|
77
|
+
|
|
78
|
+
deploy:
|
|
79
|
+
needs: build
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
environment: production # Required reviewers or wait timer enforced here
|
|
82
|
+
steps:
|
|
83
|
+
- name: Deploy
|
|
84
|
+
env:
|
|
85
|
+
# DEPLOY_KEY is an environment secret — only available because
|
|
86
|
+
# environment: production is declared on this job
|
|
87
|
+
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
|
|
88
|
+
run: echo "Deploying with environment-scoped secret"
|
|
89
|
+
prevention:
|
|
90
|
+
- 'When creating a secret, confirm its intended scope: environment secrets require the matching environment: on every job that needs them'
|
|
91
|
+
- 'If a secret is needed in a build job (no deployment environment), create it as a repository secret not an environment secret'
|
|
92
|
+
- 'Add environment: to reusable workflow caller jobs when the called workflow references environment-scoped secrets'
|
|
93
|
+
- 'Use the GitHub Settings UI to audit secret scopes when debugging empty secret values'
|
|
94
|
+
docs:
|
|
95
|
+
- url: 'https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment'
|
|
96
|
+
label: 'GitHub Docs: Using environments for deployment'
|
|
97
|
+
- url: 'https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-an-environment'
|
|
98
|
+
label: 'GitHub Docs: Creating secrets for an environment'
|
|
99
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idenvironment'
|
|
100
|
+
label: 'GitHub Docs: jobs.<job_id>.environment syntax'
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
id: runner-environment-104
|
|
2
|
+
title: 'Private container registry image (ghcr.io, Docker Hub private) requires credentials: in the container: block — a docker login step is too late'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- container
|
|
7
|
+
- ghcr
|
|
8
|
+
- private-registry
|
|
9
|
+
- credentials
|
|
10
|
+
- docker-pull
|
|
11
|
+
- job-container
|
|
12
|
+
- services
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'pull access denied.*repository does not exist or may require.*login'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'unauthorized.*authentication required|denied.*requested access to the resource is denied'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
error_messages:
|
|
19
|
+
- 'Error response from daemon: pull access denied for ghcr.io/org/image, repository does not exist or may require docker login: denied'
|
|
20
|
+
- 'Error: unauthorized: authentication required'
|
|
21
|
+
- 'Error pulling image: denied: requested access to the resource is denied'
|
|
22
|
+
- 'toomanyrequests: You have reached your pull rate limit'
|
|
23
|
+
root_cause: |
|
|
24
|
+
When a job specifies a container image via jobs.<id>.container.image:, GitHub Actions
|
|
25
|
+
pulls the image before the job starts — before any steps execute. There is no
|
|
26
|
+
opportunity to authenticate with a docker login step because steps run inside the
|
|
27
|
+
already-running container.
|
|
28
|
+
|
|
29
|
+
Many workflows attempt to call docker/login-action or similar in a step, but by
|
|
30
|
+
then the container pull has either succeeded or already failed. The login step
|
|
31
|
+
has no effect on the original image pull.
|
|
32
|
+
|
|
33
|
+
The same limitation applies to services containers (jobs.<id>.services.<id>.image:):
|
|
34
|
+
all service containers are also pulled at job startup before any steps run.
|
|
35
|
+
|
|
36
|
+
Common scenarios that hit this error:
|
|
37
|
+
- Private GitHub Container Registry (ghcr.io) images requiring a PAT with
|
|
38
|
+
read:packages scope
|
|
39
|
+
- Docker Hub private repository images (rate-limited or private-tier)
|
|
40
|
+
- Self-hosted or corporate registries requiring Basic auth
|
|
41
|
+
- AWS ECR private images (ECR is not directly supported via credentials: block —
|
|
42
|
+
see fix for the ECR-specific workaround)
|
|
43
|
+
fix: |
|
|
44
|
+
Provide credentials in the container: or services: block using the credentials: key.
|
|
45
|
+
These credentials are used at image pull time, before any steps begin.
|
|
46
|
+
|
|
47
|
+
For ghcr.io: use github.actor as username and a PAT with read:packages scope as
|
|
48
|
+
password. If the package is in the same org and the repository has package access,
|
|
49
|
+
secrets.GITHUB_TOKEN may also work.
|
|
50
|
+
|
|
51
|
+
For Docker Hub: use a Docker Hub username and access token (not password).
|
|
52
|
+
|
|
53
|
+
For AWS ECR private images: use a self-hosted runner with ECR credentials
|
|
54
|
+
pre-configured on the host, or build a public mirror of your private image.
|
|
55
|
+
The credentials: block does not support dynamic ECR token retrieval.
|
|
56
|
+
fix_code:
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: 'Authenticate to a private ghcr.io image using credentials in the container block'
|
|
59
|
+
code: |
|
|
60
|
+
jobs:
|
|
61
|
+
test:
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
container:
|
|
64
|
+
image: ghcr.io/myorg/private-runner:latest
|
|
65
|
+
# credentials: evaluated at job startup BEFORE any steps run
|
|
66
|
+
credentials:
|
|
67
|
+
username: ${{ github.actor }}
|
|
68
|
+
password: ${{ secrets.GHCR_PAT }}
|
|
69
|
+
# For same-org packages with package access enabled:
|
|
70
|
+
# password: ${{ secrets.GITHUB_TOKEN }}
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@v4
|
|
73
|
+
- run: echo "Running inside authenticated private container"
|
|
74
|
+
|
|
75
|
+
- language: yaml
|
|
76
|
+
label: 'Private Docker Hub image in a services block using credentials'
|
|
77
|
+
code: |
|
|
78
|
+
jobs:
|
|
79
|
+
integration-test:
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
services:
|
|
82
|
+
database:
|
|
83
|
+
image: myorg/private-db:5.7
|
|
84
|
+
# credentials: here too — service images are pulled before steps
|
|
85
|
+
credentials:
|
|
86
|
+
username: ${{ secrets.DOCKER_USERNAME }}
|
|
87
|
+
password: ${{ secrets.DOCKER_TOKEN }}
|
|
88
|
+
ports:
|
|
89
|
+
- 5432:5432
|
|
90
|
+
steps:
|
|
91
|
+
- uses: actions/checkout@v4
|
|
92
|
+
- run: echo "Integration test against private DB image"
|
|
93
|
+
prevention:
|
|
94
|
+
- 'Always use the credentials: block inside container: or services: for private images — a docker login step in run: is too late'
|
|
95
|
+
- 'For same-org ghcr.io images, configure package visibility to allow GITHUB_TOKEN to avoid managing a separate PAT'
|
|
96
|
+
- 'Test authentication separately with docker pull from a local machine using the same credentials before wiring into a workflow'
|
|
97
|
+
docs:
|
|
98
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idcontainer'
|
|
99
|
+
label: 'GitHub Docs: jobs.<job_id>.container.credentials'
|
|
100
|
+
- url: 'https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions'
|
|
101
|
+
label: 'GitHub Docs: Using GITHUB_TOKEN with GitHub Packages (ghcr.io)'
|
|
102
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idservicesservice_idcredentials'
|
|
103
|
+
label: 'GitHub Docs: jobs.<job_id>.services.<service_id>.credentials'
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
id: runner-environment-105
|
|
2
|
+
title: 'macOS 12 (Monterey) runner retired September 2024 — runs-on: macos-12 workflows fail or queue indefinitely'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- macos-12
|
|
7
|
+
- runner-retirement
|
|
8
|
+
- macos-monterey
|
|
9
|
+
- deprecated-runner
|
|
10
|
+
- github-hosted
|
|
11
|
+
- runs-on
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'runs-on:\s*macos-12'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'No runner matching the specified labels was found.*macos-12|Requested labels: macos-12'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- 'No runner matching the specified labels was found: macos-12'
|
|
19
|
+
- '##[error]No runner matching the specified labels was found'
|
|
20
|
+
- 'Runner not found matching labels: [macos-12]'
|
|
21
|
+
root_cause: |
|
|
22
|
+
GitHub retired the macOS 12 (Monterey) hosted runner on September 1, 2024. After
|
|
23
|
+
this date, workflows specifying runs-on: macos-12 can no longer be scheduled on a
|
|
24
|
+
GitHub-hosted runner matching that label.
|
|
25
|
+
|
|
26
|
+
Depending on repository and organization settings, affected jobs may:
|
|
27
|
+
- Fail immediately with "No runner matching the specified labels was found"
|
|
28
|
+
- Queue indefinitely waiting for a runner that will never be provisioned
|
|
29
|
+
- Show a "This job was skipped" status with no clear error message
|
|
30
|
+
|
|
31
|
+
GitHub announced the deprecation on May 20, 2024 (90+ days notice) and made it
|
|
32
|
+
official with a deprecation flag on July 1, 2024. Hard retirement occurred
|
|
33
|
+
September 1, 2024.
|
|
34
|
+
|
|
35
|
+
macOS 12 was the Monterey release. GitHub's macOS runner fleet moved to:
|
|
36
|
+
- macOS 13 (Ventura) — Intel x86-64, became the Intel baseline
|
|
37
|
+
- macOS 14 (Sonoma) — Apple Silicon M1, new default for macos-latest (Oct 2024)
|
|
38
|
+
- macOS 15 (Sequoia) — Apple Silicon M2, macos-latest as of January 2025
|
|
39
|
+
|
|
40
|
+
Common sources of this error after retirement:
|
|
41
|
+
- Long-lived workflow files written when macOS 12 was current
|
|
42
|
+
- Forks and template repositories with outdated runner labels
|
|
43
|
+
- Composite actions that pin macos-12 in their action.yml runs: block
|
|
44
|
+
- Third-party reusable workflows that have not been updated
|
|
45
|
+
fix: |
|
|
46
|
+
Replace runs-on: macos-12 with a supported macOS runner label. Choose based on
|
|
47
|
+
architecture requirements:
|
|
48
|
+
|
|
49
|
+
- macos-13 — Intel x86-64, macOS Ventura (closest to macOS 12 behavior)
|
|
50
|
+
- macos-14 — Apple Silicon M1, macOS Sonoma
|
|
51
|
+
- macos-15 — Apple Silicon M2, macOS Sequoia
|
|
52
|
+
- macos-latest — currently macOS 15 / Apple Silicon as of January 2025
|
|
53
|
+
|
|
54
|
+
IMPORTANT: macos-14 and later use Apple Silicon (M1/M2). If your workflow depends
|
|
55
|
+
on Intel x86-64 architecture (Homebrew formula paths differ, Rosetta 2 needed for
|
|
56
|
+
old binaries, or x86-specific compiler flags), migrate to macos-13, not macos-latest.
|
|
57
|
+
|
|
58
|
+
After migrating, verify:
|
|
59
|
+
- Homebrew default prefix changed from /usr/local (Intel) to /opt/homebrew (ARM)
|
|
60
|
+
- Xcode version availability — use actions/setup-xcode for explicit version pinning
|
|
61
|
+
- System Python and Ruby versions differ between macOS generations
|
|
62
|
+
- Any hardcoded SDKROOT or architecture flags targeting x86-64
|
|
63
|
+
fix_code:
|
|
64
|
+
- language: yaml
|
|
65
|
+
label: 'Replace retired macos-12 with macos-13 (Intel) or macos-14/15 (Apple Silicon)'
|
|
66
|
+
code: |
|
|
67
|
+
jobs:
|
|
68
|
+
build-intel:
|
|
69
|
+
# Before: runs-on: macos-12 ← retired September 1, 2024
|
|
70
|
+
# Intel x86-64 — closest behavioral match to macOS 12:
|
|
71
|
+
runs-on: macos-13
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@v4
|
|
74
|
+
- name: Build
|
|
75
|
+
run: make build
|
|
76
|
+
|
|
77
|
+
build-arm:
|
|
78
|
+
# Apple Silicon M1/M2 — use for new projects or ARM-compatible builds:
|
|
79
|
+
runs-on: macos-latest # macOS 15 / Apple Silicon as of Jan 2025
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@v4
|
|
82
|
+
- name: Build
|
|
83
|
+
run: make build
|
|
84
|
+
|
|
85
|
+
- language: yaml
|
|
86
|
+
label: 'Matrix across macOS versions to validate compatibility before committing to one'
|
|
87
|
+
code: |
|
|
88
|
+
jobs:
|
|
89
|
+
test:
|
|
90
|
+
strategy:
|
|
91
|
+
matrix:
|
|
92
|
+
# Test Intel (13) and Apple Silicon (14) in parallel
|
|
93
|
+
os: [macos-13, macos-14]
|
|
94
|
+
fail-fast: false
|
|
95
|
+
runs-on: ${{ matrix.os }}
|
|
96
|
+
steps:
|
|
97
|
+
- uses: actions/checkout@v4
|
|
98
|
+
- name: Test on ${{ matrix.os }}
|
|
99
|
+
run: make test
|
|
100
|
+
prevention:
|
|
101
|
+
- 'Subscribe to the GitHub Changelog (github.blog/changelog) for runner retirement notices — typically 90+ days notice'
|
|
102
|
+
- 'Pin to a specific macOS version (macos-13, macos-14) rather than macos-latest for reproducible builds; macos-latest advances'
|
|
103
|
+
- 'Be aware of architecture differences: macos-13 is Intel x86-64; macos-14 and later are Apple Silicon'
|
|
104
|
+
- 'Search workflow files periodically for retired labels: look for macos-12, macos-11, ubuntu-18.04, windows-2019'
|
|
105
|
+
docs:
|
|
106
|
+
- url: 'https://github.blog/changelog/2024-05-20-actions-upcoming-changes-to-github-hosted-macos-runners/'
|
|
107
|
+
label: 'GitHub Changelog: Upcoming changes to macOS runners (May 2024)'
|
|
108
|
+
- url: 'https://github.blog/changelog/2024-07-01-github-actions-macos-12-is-now-deprecated/'
|
|
109
|
+
label: 'GitHub Changelog: macOS 12 is now deprecated (July 2024)'
|
|
110
|
+
- url: 'https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources'
|
|
111
|
+
label: 'GitHub Docs: Supported runners and hardware resources'
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
id: silent-failures-052
|
|
2
|
+
title: 'Step outputs referenced in the job-level env: block are always empty string — job env is evaluated before any steps run'
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- env
|
|
7
|
+
- steps-outputs
|
|
8
|
+
- job-level
|
|
9
|
+
- context-evaluation
|
|
10
|
+
- expression
|
|
11
|
+
- environment-variables
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'steps\.[a-z0-9_-]+\.outputs\.[a-z0-9_-]+'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "(No error — steps.<id>.outputs.<name> silently resolves to '' when used in a job-level env: block)"
|
|
17
|
+
root_cause: |
|
|
18
|
+
GitHub Actions evaluates the jobs.<id>.env: block once at job initialization, before
|
|
19
|
+
any steps begin executing. At that point, the steps context exists but all step
|
|
20
|
+
outputs are empty — no step has run yet, so no outputs have been set.
|
|
21
|
+
|
|
22
|
+
This means that ${{ steps.my-step.outputs.value }} in the jobs.<id>.env: block
|
|
23
|
+
always resolves to '' regardless of what the step later produces. The job does not
|
|
24
|
+
fail or warn — the environment variable is simply set to empty string and all steps
|
|
25
|
+
that reference it via $VAR or %VAR% receive nothing.
|
|
26
|
+
|
|
27
|
+
This is a context availability limitation documented by GitHub, but it is easy to
|
|
28
|
+
miss because the expression syntax is valid and the job runs without error.
|
|
29
|
+
|
|
30
|
+
Affected env: scopes (evaluated before steps run):
|
|
31
|
+
- jobs.<id>.env: — job-level env block
|
|
32
|
+
- The workflow-level env: block also cannot access steps.*, for the same reason
|
|
33
|
+
|
|
34
|
+
Env scopes that CAN access step outputs (evaluated per-step):
|
|
35
|
+
- jobs.<id>.steps.<id>.env: — step-level env block, evaluated when that step runs
|
|
36
|
+
|
|
37
|
+
Common mistake: a developer sets a job-level env var to a computed step output
|
|
38
|
+
(e.g., a parsed version string or a generated artifact path) and then uses that
|
|
39
|
+
var in multiple subsequent steps, not realizing it is always empty.
|
|
40
|
+
fix: |
|
|
41
|
+
Move the env: block referencing step outputs from job level down to the individual
|
|
42
|
+
step level. Step-level env: blocks are evaluated when that step runs, so earlier
|
|
43
|
+
step outputs are already populated.
|
|
44
|
+
|
|
45
|
+
If the same value is needed across many steps, pass it via $GITHUB_OUTPUT and
|
|
46
|
+
reference it inline with ${{ steps.step-id.outputs.name }} in each step's run:.
|
|
47
|
+
fix_code:
|
|
48
|
+
- language: yaml
|
|
49
|
+
label: 'Move env that references step outputs from job level to step level'
|
|
50
|
+
code: |
|
|
51
|
+
jobs:
|
|
52
|
+
build:
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
|
|
55
|
+
# WRONG: job-level env block — evaluated before any step runs
|
|
56
|
+
# steps.version.outputs.tag is '' here, no matter what the step produces
|
|
57
|
+
# env:
|
|
58
|
+
# RELEASE_TAG: ${{ steps.version.outputs.tag }}
|
|
59
|
+
|
|
60
|
+
steps:
|
|
61
|
+
- name: Compute release tag
|
|
62
|
+
id: version
|
|
63
|
+
run: echo "tag=v1.2.3" >> $GITHUB_OUTPUT
|
|
64
|
+
|
|
65
|
+
- name: Build with release tag
|
|
66
|
+
# CORRECT: step-level env block — evaluated when this step runs
|
|
67
|
+
# steps.version.outputs.tag is populated by the prior step at this point
|
|
68
|
+
env:
|
|
69
|
+
RELEASE_TAG: ${{ steps.version.outputs.tag }}
|
|
70
|
+
run: echo "Building $RELEASE_TAG"
|
|
71
|
+
|
|
72
|
+
- name: Publish with release tag
|
|
73
|
+
env:
|
|
74
|
+
RELEASE_TAG: ${{ steps.version.outputs.tag }}
|
|
75
|
+
run: echo "Publishing $RELEASE_TAG"
|
|
76
|
+
|
|
77
|
+
- language: yaml
|
|
78
|
+
label: 'Use inline expression in run steps to avoid repeating step-level env blocks'
|
|
79
|
+
code: |
|
|
80
|
+
jobs:
|
|
81
|
+
release:
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
steps:
|
|
84
|
+
- name: Resolve version
|
|
85
|
+
id: version
|
|
86
|
+
run: echo "tag=v2.0.0" >> $GITHUB_OUTPUT
|
|
87
|
+
|
|
88
|
+
- name: Tag Docker image
|
|
89
|
+
# Reference step output directly in run — no env: block needed
|
|
90
|
+
run: echo "Tagging image as ${{ steps.version.outputs.tag }}"
|
|
91
|
+
|
|
92
|
+
- name: Push Docker image
|
|
93
|
+
run: echo "Pushing tag ${{ steps.version.outputs.tag }}"
|
|
94
|
+
prevention:
|
|
95
|
+
- 'Never reference steps.<id>.outputs.* in the jobs.<id>.env: block — use step-level env: or inline expressions in run: instead'
|
|
96
|
+
- 'Enable actionlint locally to catch step output references in job-level env blocks before they reach CI'
|
|
97
|
+
- 'When the same step output is needed in many steps, document it with a comment on the generating step so readers know where to look'
|
|
98
|
+
docs:
|
|
99
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#context-availability'
|
|
100
|
+
label: 'GitHub Docs: Context availability — which contexts are accessible at each location'
|
|
101
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables'
|
|
102
|
+
label: 'GitHub Docs: Store information in variables'
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
id: triggers-037
|
|
2
|
+
title: 'on.release types: [published] does not fire for pre-releases — prereleased is a separate event type'
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- release
|
|
7
|
+
- prereleased
|
|
8
|
+
- published
|
|
9
|
+
- event-types
|
|
10
|
+
- pre-release
|
|
11
|
+
- trigger-filter
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'on:\s*\n\s+release:\s*\n\s+types:\s*\[.*published(?!.*prereleased)'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "(No error — workflow simply does not trigger when a pre-release is published via the GitHub UI or API)"
|
|
17
|
+
root_cause: |
|
|
18
|
+
GitHub's release event distinguishes two separate activity types for publishing:
|
|
19
|
+
- published: fires when a full (non-pre) release is published (is_prerelease: false)
|
|
20
|
+
- prereleased: fires when a pre-release is published (is_prerelease: true)
|
|
21
|
+
|
|
22
|
+
The published type does NOT include pre-releases. A workflow with
|
|
23
|
+
types: [published] will never fire when "Set as a pre-release" is checked in
|
|
24
|
+
the GitHub UI, or when a release is created with prerelease: true via the Releases API.
|
|
25
|
+
|
|
26
|
+
This silently skips release automation (npm publish, Docker image push, deployment
|
|
27
|
+
pipelines) for pre-release versions (e.g., v2.0.0-rc.1, v1.5.0-beta.3).
|
|
28
|
+
No warning, no skipped-run entry in the Actions tab — the event simply never arrives.
|
|
29
|
+
|
|
30
|
+
Note: the created type fires for both releases AND pre-releases when they are first
|
|
31
|
+
created as drafts, not when published. The released type fires for both
|
|
32
|
+
published and prereleased events and is the simplest way to catch both.
|
|
33
|
+
fix: |
|
|
34
|
+
To fire on BOTH full releases and pre-releases: add prereleased to the types list,
|
|
35
|
+
or switch to types: [released] which fires for both without listing each type.
|
|
36
|
+
|
|
37
|
+
To fire ONLY on pre-releases: use types: [prereleased].
|
|
38
|
+
|
|
39
|
+
To distinguish inside the workflow: check github.event.release.prerelease (boolean).
|
|
40
|
+
fix_code:
|
|
41
|
+
- language: yaml
|
|
42
|
+
label: 'Include prereleased to fire for both full releases and pre-releases'
|
|
43
|
+
code: |
|
|
44
|
+
on:
|
|
45
|
+
release:
|
|
46
|
+
# published fires only for full releases
|
|
47
|
+
# prereleased fires only for pre-releases
|
|
48
|
+
# List both to catch all published releases
|
|
49
|
+
types: [published, prereleased]
|
|
50
|
+
|
|
51
|
+
jobs:
|
|
52
|
+
publish:
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
steps:
|
|
55
|
+
- name: Set dist-tag based on release type
|
|
56
|
+
id: meta
|
|
57
|
+
run: |
|
|
58
|
+
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
|
|
59
|
+
echo "dist_tag=next" >> $GITHUB_OUTPUT
|
|
60
|
+
else
|
|
61
|
+
echo "dist_tag=latest" >> $GITHUB_OUTPUT
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
- language: yaml
|
|
65
|
+
label: 'Use released to always fire for both release types without listing each'
|
|
66
|
+
code: |
|
|
67
|
+
on:
|
|
68
|
+
release:
|
|
69
|
+
# released is equivalent to [published, prereleased] — fires for both
|
|
70
|
+
types: [released]
|
|
71
|
+
prevention:
|
|
72
|
+
- 'Always include prereleased in types: if pre-release automation is expected (npm publish, Docker push, deploy)'
|
|
73
|
+
- 'Test release workflows by creating a GitHub pre-release — verify the workflow appears in the Actions tab'
|
|
74
|
+
- 'Use github.event.release.prerelease (boolean) to branch behavior inside the workflow rather than relying on separate trigger types'
|
|
75
|
+
docs:
|
|
76
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#release'
|
|
77
|
+
label: 'GitHub Docs: release event and activity types'
|
|
78
|
+
- url: 'https://docs.github.com/en/rest/releases/releases#create-a-release'
|
|
79
|
+
label: 'GitHub REST API: Create a release (prerelease field)'
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
id: triggers-038
|
|
2
|
+
title: 'workflow_run.conclusion is null when the upstream run was cancelled before any job started — if: checks silently skip'
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow-run
|
|
7
|
+
- conclusion
|
|
8
|
+
- cancelled
|
|
9
|
+
- null-conclusion
|
|
10
|
+
- if-condition
|
|
11
|
+
- cross-workflow
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'github\.event\.workflow_run\.conclusion\s*==\s*[''"]success[''"]'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'github\.event\.workflow_run\.conclusion\s*==\s*[''"]failure[''"]'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "(No error — triggered workflow runs but all jobs/steps with if: github.event.workflow_run.conclusion == 'success' silently skip)"
|
|
19
|
+
root_cause: |
|
|
20
|
+
When a downstream workflow uses on: workflow_run (types: [completed]), it fires
|
|
21
|
+
whenever an upstream workflow run reaches a terminal state. The
|
|
22
|
+
github.event.workflow_run.conclusion field is expected to reflect the outcome of
|
|
23
|
+
that upstream run.
|
|
24
|
+
|
|
25
|
+
However, if the upstream run was cancelled before any job started — for example,
|
|
26
|
+
due to a concurrent push triggering cancel-in-progress on the queued run, or a
|
|
27
|
+
manual UI cancellation before the run left the queue — the conclusion field is
|
|
28
|
+
null, not "cancelled".
|
|
29
|
+
|
|
30
|
+
GitHub's API returns: { "conclusion": null, "status": "cancelled" } for runs
|
|
31
|
+
cancelled while still queued. This is documented behavior: conclusion is only set
|
|
32
|
+
when at least one job has run to completion.
|
|
33
|
+
|
|
34
|
+
The effect on downstream conditional logic:
|
|
35
|
+
if: github.event.workflow_run.conclusion == 'success' → false (null != string)
|
|
36
|
+
if: github.event.workflow_run.conclusion == 'failure' → false
|
|
37
|
+
if: github.event.workflow_run.conclusion == 'cancelled' → also false (null != string)
|
|
38
|
+
if: github.event.workflow_run.conclusion != 'success' → TRUE (null != string)
|
|
39
|
+
|
|
40
|
+
The downstream workflow's workflow_run completed event fires and the workflow
|
|
41
|
+
starts, but every job with a success/failure/cancelled equality check is silently
|
|
42
|
+
skipped. A CD pipeline gated on CI success may appear to trigger but produce no
|
|
43
|
+
deployment with no error surfaced to the developer.
|
|
44
|
+
|
|
45
|
+
This can also silently allow deployment jobs protected by
|
|
46
|
+
conclusion != 'failure' to run when the upstream was an early cancellation.
|
|
47
|
+
fix: |
|
|
48
|
+
Treat workflow_run.conclusion as nullable. Always verify it is not null before
|
|
49
|
+
comparing to an expected value:
|
|
50
|
+
|
|
51
|
+
Option A — Explicit null check (most readable):
|
|
52
|
+
if: github.event.workflow_run.conclusion != null && github.event.workflow_run.conclusion == 'success'
|
|
53
|
+
|
|
54
|
+
Option B — contains() with fromJSON allowlist (handles null gracefully; null is
|
|
55
|
+
not in the list, so the condition evaluates false without error):
|
|
56
|
+
if: contains(fromJSON('["success"]'), github.event.workflow_run.conclusion)
|
|
57
|
+
|
|
58
|
+
Option C — Guard step that fails fast and visibly when conclusion is unexpected,
|
|
59
|
+
so silent skips become explicit failures:
|
|
60
|
+
- run: |
|
|
61
|
+
if [ "$CONCLUSION" != "success" ]; then
|
|
62
|
+
echo "Upstream conclusion was: $CONCLUSION"
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
env:
|
|
66
|
+
CONCLUSION: ${{ github.event.workflow_run.conclusion }}
|
|
67
|
+
fix_code:
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: 'Use contains() with fromJSON to handle null conclusion safely'
|
|
70
|
+
code: |
|
|
71
|
+
on:
|
|
72
|
+
workflow_run:
|
|
73
|
+
workflows: ['CI']
|
|
74
|
+
types: [completed]
|
|
75
|
+
|
|
76
|
+
jobs:
|
|
77
|
+
deploy:
|
|
78
|
+
# contains() returns false when conclusion is null — no null-equality error
|
|
79
|
+
if: |
|
|
80
|
+
contains(fromJSON('["success"]'), github.event.workflow_run.conclusion) &&
|
|
81
|
+
github.event.workflow_run.head_branch == 'main'
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
steps:
|
|
84
|
+
- name: Deploy
|
|
85
|
+
run: echo "Deploying after confirmed CI success"
|
|
86
|
+
|
|
87
|
+
- language: yaml
|
|
88
|
+
label: 'Guard step that fails visibly on null or unexpected conclusion'
|
|
89
|
+
code: |
|
|
90
|
+
on:
|
|
91
|
+
workflow_run:
|
|
92
|
+
workflows: ['CI']
|
|
93
|
+
types: [completed]
|
|
94
|
+
|
|
95
|
+
jobs:
|
|
96
|
+
verify-conclusion:
|
|
97
|
+
runs-on: ubuntu-latest
|
|
98
|
+
steps:
|
|
99
|
+
- name: Check upstream conclusion
|
|
100
|
+
env:
|
|
101
|
+
CONCLUSION: ${{ github.event.workflow_run.conclusion }}
|
|
102
|
+
run: |
|
|
103
|
+
echo "Upstream workflow conclusion: $CONCLUSION"
|
|
104
|
+
if [ "$CONCLUSION" != "success" ]; then
|
|
105
|
+
echo "Expected 'success', got '$CONCLUSION' (possibly null if cancelled before job start)"
|
|
106
|
+
exit 1
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
deploy:
|
|
110
|
+
needs: verify-conclusion
|
|
111
|
+
runs-on: ubuntu-latest
|
|
112
|
+
steps:
|
|
113
|
+
- name: Deploy
|
|
114
|
+
run: echo "Deploying"
|
|
115
|
+
prevention:
|
|
116
|
+
- 'Always treat workflow_run.conclusion as nullable — a run cancelled while queued has conclusion: null, not "cancelled"'
|
|
117
|
+
- 'Use contains(fromJSON(...), github.event.workflow_run.conclusion) instead of == equality — contains() handles null safely'
|
|
118
|
+
- 'Add a guard step that prints the actual conclusion value to make silent skips visible during debugging'
|
|
119
|
+
- 'Prefer repository_dispatch for cross-workflow chaining when you need explicit control over the payload and conclusion semantics'
|
|
120
|
+
docs:
|
|
121
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run'
|
|
122
|
+
label: 'GitHub Docs: workflow_run event — conclusion field behavior'
|
|
123
|
+
- url: 'https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#get-a-workflow-run'
|
|
124
|
+
label: 'GitHub REST API: Workflow run — conclusion is nullable'
|
|
125
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#github-context'
|
|
126
|
+
label: 'GitHub Docs: github.event.workflow_run context properties'
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
id: yaml-syntax-036
|
|
2
|
+
title: 'run-name: using github.event.head_commit.message shows "undefined" for schedule and workflow_dispatch events'
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- run-name
|
|
7
|
+
- head-commit-message
|
|
8
|
+
- schedule
|
|
9
|
+
- workflow-dispatch
|
|
10
|
+
- null-property
|
|
11
|
+
- expression
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'run-name:.*head_commit\.message'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "(Run title in the UI displays 'undefined' or 'Deploy by @actor from undefined' — no log error is emitted)"
|
|
17
|
+
root_cause: |
|
|
18
|
+
The run-name: workflow key (introduced October 2022) lets you set a custom title
|
|
19
|
+
for each workflow run using GitHub Actions expressions. The GitHub Docs examples
|
|
20
|
+
include the pattern:
|
|
21
|
+
|
|
22
|
+
run-name: Deploy by @${{ github.actor }} from ${{ github.event.head_commit.message }}
|
|
23
|
+
|
|
24
|
+
However, github.event.head_commit is ONLY populated for on: push events. For
|
|
25
|
+
on: schedule, on: workflow_dispatch, on: pull_request, on: release, and most other
|
|
26
|
+
event types, the head_commit object is absent from the event payload. Accessing
|
|
27
|
+
github.event.head_commit.message on a null object evaluates to '' in some contexts
|
|
28
|
+
but renders as "undefined" in the Actions UI run title.
|
|
29
|
+
|
|
30
|
+
The result is a workflow run title like:
|
|
31
|
+
"Deploy by @octocat from undefined"
|
|
32
|
+
or simply "undefined" — confusing in the Actions tab and audit logs.
|
|
33
|
+
|
|
34
|
+
The workflow parses without error because the expression is syntactically valid.
|
|
35
|
+
The problem only surfaces at runtime when a non-push event triggers the workflow.
|
|
36
|
+
This is especially common in workflows that have both on: push and on: schedule or
|
|
37
|
+
on: workflow_dispatch triggers.
|
|
38
|
+
fix: |
|
|
39
|
+
Use the || short-circuit operator to provide fallback values for event types where
|
|
40
|
+
head_commit is absent. GitHub Actions expressions treat || as a null/false fallback:
|
|
41
|
+
|
|
42
|
+
run-name: "${{ github.event.head_commit.message || github.event.inputs.reason || 'Scheduled run' }}"
|
|
43
|
+
|
|
44
|
+
Prefer contexts that are populated for ALL event types:
|
|
45
|
+
- github.actor — always set (the user or app that triggered the run)
|
|
46
|
+
- github.ref_name — always set (branch or tag short name)
|
|
47
|
+
- github.run_number — always set (monotonically increasing per workflow)
|
|
48
|
+
- github.event.inputs.* — set for workflow_dispatch when inputs are defined
|
|
49
|
+
fix_code:
|
|
50
|
+
- language: yaml
|
|
51
|
+
label: 'Add || fallback so run-name is readable for all trigger types'
|
|
52
|
+
code: |
|
|
53
|
+
name: Deploy
|
|
54
|
+
# Without || fallback, schedule and workflow_dispatch show "undefined"
|
|
55
|
+
# github.event.head_commit is ONLY available on push events
|
|
56
|
+
run-name: "${{ github.event.head_commit.message || github.event.inputs.reason || 'Scheduled run' }}"
|
|
57
|
+
on:
|
|
58
|
+
push:
|
|
59
|
+
branches: [main]
|
|
60
|
+
schedule:
|
|
61
|
+
- cron: '0 8 * * 1'
|
|
62
|
+
workflow_dispatch:
|
|
63
|
+
inputs:
|
|
64
|
+
reason:
|
|
65
|
+
description: 'Reason for manual trigger'
|
|
66
|
+
required: false
|
|
67
|
+
jobs:
|
|
68
|
+
deploy:
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/checkout@v4
|
|
72
|
+
|
|
73
|
+
- language: yaml
|
|
74
|
+
label: 'Use always-populated contexts for a universally safe run-name'
|
|
75
|
+
code: |
|
|
76
|
+
name: Deploy
|
|
77
|
+
# github.actor, github.ref_name, and github.run_number are set for every event
|
|
78
|
+
run-name: '${{ github.actor }} on ${{ github.ref_name }} (#${{ github.run_number }})'
|
|
79
|
+
on:
|
|
80
|
+
push:
|
|
81
|
+
schedule:
|
|
82
|
+
- cron: '0 8 * * 1'
|
|
83
|
+
workflow_dispatch:
|
|
84
|
+
jobs:
|
|
85
|
+
deploy:
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@v4
|
|
89
|
+
prevention:
|
|
90
|
+
- 'Test run-name expressions against every trigger in your on: block — head_commit is only available for push events'
|
|
91
|
+
- 'Always provide a || fallback: github.event.head_commit.message || ''Scheduled run'' handles both push and non-push'
|
|
92
|
+
- 'Prefer github.actor, github.ref_name, and github.run_number — these are populated for every event type'
|
|
93
|
+
- 'Check context availability in the GitHub Docs contexts reference before using event-specific properties in run-name'
|
|
94
|
+
docs:
|
|
95
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#run-name'
|
|
96
|
+
label: 'GitHub Docs: Workflow syntax — run-name'
|
|
97
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#github-context'
|
|
98
|
+
label: 'GitHub Docs: github context — event-specific property availability'
|
|
99
|
+
- url: 'https://github.blog/changelog/2022-10-05-github-actions-run-name-and-workflow-run-title/'
|
|
100
|
+
label: 'GitHub Changelog: run-name and workflow run title (October 2022)'
|
package/package.json
CHANGED