@htekdev/actions-debugger 1.0.100 → 1.0.102
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-056.yml +113 -0
- package/errors/caching-artifacts/caching-artifacts-057.yml +98 -0
- package/errors/permissions-auth/permissions-auth-059.yml +136 -0
- package/errors/permissions-auth/permissions-auth-060.yml +115 -0
- package/errors/runner-environment/homebrew-auto-update-macos-ci-slow.yml +84 -0
- package/errors/runner-environment/python312-ast-str-num-removed-ubuntu24.yml +74 -0
- package/errors/runner-environment/runner-environment-170.yml +93 -0
- package/errors/runner-environment/setup-go-eol-version-not-in-tool-cache.yml +78 -0
- package/errors/silent-failures/silent-failures-091.yml +94 -0
- package/errors/triggers/triggers-067.yml +134 -0
- package/errors/yaml-syntax/env-context-unavailable-defaults-run-working-directory.yml +113 -0
- package/errors/yaml-syntax/yaml-syntax-064.yml +119 -0
- package/package.json +1 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
id: caching-artifacts-056
|
|
2
|
+
title: 'actions/cache v1 and v2 deprecated — workflows fail hard with "automatically failed" error'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- actions/cache
|
|
7
|
+
- deprecated
|
|
8
|
+
- v1
|
|
9
|
+
- v2
|
|
10
|
+
- hard-failure
|
|
11
|
+
- breaking-change
|
|
12
|
+
- upgrade
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'automatically failed.*deprecated version.*actions/cache'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'deprecated version of.{0,30}actions/cache'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
- regex: 'actions/cache@v[12]\b'
|
|
19
|
+
flags: 'i'
|
|
20
|
+
error_messages:
|
|
21
|
+
- "This request has been automatically failed because it uses a deprecated version of `actions/cache: 6849a6489940f00c2f30c0fb92c6274307ccb58a`"
|
|
22
|
+
- "Error: This request has been automatically failed because it uses a deprecated version of `actions/cache`"
|
|
23
|
+
root_cause: |
|
|
24
|
+
GitHub announced in December 2024 (GitHub Changelog) that `actions/cache` versions 1 and 2
|
|
25
|
+
were deprecated and would be hard-failed starting February 1, 2025. Any workflow still
|
|
26
|
+
pinned to `actions/cache@v1` or `actions/cache@v2` (or to a full SHA that resolves to
|
|
27
|
+
those versions) now fails immediately with a hard error — no cache operation is attempted
|
|
28
|
+
and the entire step fails.
|
|
29
|
+
|
|
30
|
+
The error message includes the SHA of the deprecated action version:
|
|
31
|
+
"This request has been automatically failed because it uses a deprecated version
|
|
32
|
+
of `actions/cache: <SHA>`"
|
|
33
|
+
|
|
34
|
+
This is a hard failure, not a graceful degradation — the step exit code is non-zero and
|
|
35
|
+
causes the job to fail unless `continue-on-error: true` is set on the step (which is
|
|
36
|
+
not a recommended fix).
|
|
37
|
+
|
|
38
|
+
Common reasons workflows are still on v1/v2:
|
|
39
|
+
1. The workflow was written years ago and never updated
|
|
40
|
+
2. The action is referenced by a full commit SHA from before v3 was released
|
|
41
|
+
3. A reusable workflow or composite action calls an outdated dependency that pins v1/v2
|
|
42
|
+
4. `Dependabot` is not enabled on the repo for GitHub Actions
|
|
43
|
+
|
|
44
|
+
Note: `actions/cache@v3` and `actions/cache@v4` are both supported. V4 is the current
|
|
45
|
+
recommended version.
|
|
46
|
+
fix: |
|
|
47
|
+
Upgrade all `actions/cache` references from v1 or v2 to v4 (recommended) or v3.
|
|
48
|
+
Search your repository for `actions/cache@v1` and `actions/cache@v2` references,
|
|
49
|
+
including in nested composite actions and reusable workflows.
|
|
50
|
+
|
|
51
|
+
If you are migrating from v3 to v4, note the breaking changes documented in the
|
|
52
|
+
v3-to-v4 migration guide (see caching-artifacts-009). The v1/v2 to v4 migration
|
|
53
|
+
follows the same v3-to-v4 guidance since v3 and v4 share the same input API except
|
|
54
|
+
for `save-always`.
|
|
55
|
+
|
|
56
|
+
Enable Dependabot for GitHub Actions updates to automatically receive future
|
|
57
|
+
deprecation PRs.
|
|
58
|
+
fix_code:
|
|
59
|
+
- language: yaml
|
|
60
|
+
label: 'Problem: workflow pinned to deprecated actions/cache v1 or v2'
|
|
61
|
+
code: |
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v4
|
|
64
|
+
|
|
65
|
+
# FAILS: v1 and v2 are deprecated — hard failure since Feb 1, 2025
|
|
66
|
+
- uses: actions/cache@v1
|
|
67
|
+
with:
|
|
68
|
+
path: ~/.npm
|
|
69
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
70
|
+
|
|
71
|
+
# Also fails — v2 is deprecated
|
|
72
|
+
- uses: actions/cache@v2
|
|
73
|
+
with:
|
|
74
|
+
path: ~/.npm
|
|
75
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
76
|
+
- language: yaml
|
|
77
|
+
label: 'Fix: upgrade to actions/cache v4 (recommended)'
|
|
78
|
+
code: |
|
|
79
|
+
steps:
|
|
80
|
+
- uses: actions/checkout@v4
|
|
81
|
+
|
|
82
|
+
# CORRECT: use v4 (or v3 if v3-to-v4 migration is not yet done)
|
|
83
|
+
- uses: actions/cache@v4
|
|
84
|
+
with:
|
|
85
|
+
path: ~/.npm
|
|
86
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
87
|
+
restore-keys: |
|
|
88
|
+
${{ runner.os }}-npm-
|
|
89
|
+
- language: yaml
|
|
90
|
+
label: 'Dependabot config to automatically receive Actions version updates'
|
|
91
|
+
code: |
|
|
92
|
+
# .github/dependabot.yml
|
|
93
|
+
version: 2
|
|
94
|
+
updates:
|
|
95
|
+
- package-ecosystem: github-actions
|
|
96
|
+
directory: /
|
|
97
|
+
schedule:
|
|
98
|
+
interval: weekly
|
|
99
|
+
# Dependabot will open PRs to upgrade actions/cache@v1/v2 → v4 automatically
|
|
100
|
+
prevention:
|
|
101
|
+
- "Enable Dependabot for GitHub Actions in `.github/dependabot.yml` to receive automated upgrade PRs."
|
|
102
|
+
- "Search your `.github/workflows/` directory for `actions/cache@v1` and `actions/cache@v2` before the deprecation deadline."
|
|
103
|
+
- "Check composite actions and reusable workflows — they may internally call deprecated cache versions."
|
|
104
|
+
- "Audit SHA-pinned action references to ensure the SHA does not point to a v1/v2 release."
|
|
105
|
+
docs:
|
|
106
|
+
- url: 'https://github.blog/changelog/2024-12-05-notice-of-upcoming-releases-and-breaking-changes-for-github-actions/#actions-cache-v1-v2-and-actions-toolkit-cache-package-closing-down'
|
|
107
|
+
label: 'GitHub Changelog Dec 2024: actions/cache v1/v2 deprecation notice'
|
|
108
|
+
- url: 'https://github.com/actions/cache/discussions/1510'
|
|
109
|
+
label: 'actions/cache Discussion #1510: deprecated version hard failure reports'
|
|
110
|
+
- url: 'https://github.com/actions/cache'
|
|
111
|
+
label: 'actions/cache repository — upgrade instructions'
|
|
112
|
+
- url: 'https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot'
|
|
113
|
+
label: 'GitHub Docs: Keeping your actions up to date with Dependabot'
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
id: caching-artifacts-057
|
|
2
|
+
title: '`actions/cache` Never Refreshes Cached Content — Cache Keys Are Immutable Once Stored'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- stale
|
|
8
|
+
- immutable
|
|
9
|
+
- cache-hit
|
|
10
|
+
- refresh
|
|
11
|
+
- node-modules
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'cache-hit.*true.*post.*skip'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "Cache hit occurred on the primary key, not saving cache."
|
|
17
|
+
- "Cache hit occurred on the primary key runner-os-node-"
|
|
18
|
+
root_cause: |
|
|
19
|
+
`actions/cache` uses immutable cache keys. Once a cache entry is stored for a given
|
|
20
|
+
key, it cannot be updated or overwritten — any subsequent run that produces an exact
|
|
21
|
+
key match will restore the cached content and the post step will NOT save a new cache
|
|
22
|
+
entry because one already exists for that key.
|
|
23
|
+
|
|
24
|
+
This means cached content (node_modules, pip packages, Maven artifacts, etc.) is
|
|
25
|
+
frozen at the point of first creation and will not reflect package updates until:
|
|
26
|
+
- The cache entry expires (GitHub-hosted caches expire after 7 days of no access)
|
|
27
|
+
- The cache key changes (e.g., lockfile hash changes)
|
|
28
|
+
- The cache entry is manually deleted via the GitHub UI or API
|
|
29
|
+
|
|
30
|
+
Common misconception: developers expect that running `npm ci` inside a cached
|
|
31
|
+
node_modules hit will update the cache if packages changed. It won't. The cache key
|
|
32
|
+
(usually `${{ hashFiles('package-lock.json') }}`) must change for a new cache to be
|
|
33
|
+
saved.
|
|
34
|
+
|
|
35
|
+
Related gotcha: if restore-keys produce a partial hit (different key prefix), the
|
|
36
|
+
post step DOES save a new cache — but for the full key, not the partial restore key.
|
|
37
|
+
This creates unexpected cache churn from the developer's perspective where cache hit
|
|
38
|
+
rate is high but actual content is outdated.
|
|
39
|
+
|
|
40
|
+
This is intentional GitHub design but frequently misunderstood, leading to:
|
|
41
|
+
- Stale node_modules with old transitive dependencies
|
|
42
|
+
- pip packages that don't receive security patches within 7 days
|
|
43
|
+
- Build artifacts compiled against old toolchain versions
|
|
44
|
+
fix: |
|
|
45
|
+
To force periodic cache refresh without changing your lockfile, use one of these patterns:
|
|
46
|
+
|
|
47
|
+
1. Include a date-based component in the cache key (weekly rotation).
|
|
48
|
+
2. Use `actions/cache/restore` + `actions/cache/save` with explicit control of when
|
|
49
|
+
to save (allows saving even on hits).
|
|
50
|
+
3. Delete the cache manually via GitHub UI (Actions > Caches) or the API when you
|
|
51
|
+
need an immediate refresh.
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "Weekly-rotating cache key to prevent indefinitely stale caches"
|
|
55
|
+
code: |
|
|
56
|
+
- name: Get week number for cache rotation
|
|
57
|
+
id: date
|
|
58
|
+
run: echo "week=$(date +'%Y-%U')" >> "$GITHUB_OUTPUT"
|
|
59
|
+
|
|
60
|
+
- name: Cache node_modules (refreshes weekly)
|
|
61
|
+
uses: actions/cache@v4
|
|
62
|
+
with:
|
|
63
|
+
path: ~/.npm
|
|
64
|
+
key: ${{ runner.os }}-node-${{ steps.date.outputs.week }}-${{ hashFiles('**/package-lock.json') }}
|
|
65
|
+
restore-keys: |
|
|
66
|
+
${{ runner.os }}-node-${{ steps.date.outputs.week }}-
|
|
67
|
+
${{ runner.os }}-node-
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: "Separate restore + save for explicit cache update control"
|
|
70
|
+
code: |
|
|
71
|
+
- name: Restore cache
|
|
72
|
+
id: cache-restore
|
|
73
|
+
uses: actions/cache/restore@v4
|
|
74
|
+
with:
|
|
75
|
+
path: ~/.npm
|
|
76
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
77
|
+
restore-keys: ${{ runner.os }}-node-
|
|
78
|
+
|
|
79
|
+
- run: npm ci
|
|
80
|
+
|
|
81
|
+
- name: Save updated cache (always saves, even on hit)
|
|
82
|
+
uses: actions/cache/save@v4
|
|
83
|
+
if: always()
|
|
84
|
+
with:
|
|
85
|
+
path: ~/.npm
|
|
86
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}-${{ github.run_id }}
|
|
87
|
+
prevention:
|
|
88
|
+
- "Understand that GitHub cache keys are immutable — a key that already exists in the cache will never be overwritten"
|
|
89
|
+
- "Use time-based or run-id-based cache key suffixes when cached content must be refreshed more frequently than lockfile changes"
|
|
90
|
+
- "Monitor cache age in the GitHub UI (Actions > Caches) and manually purge entries that appear stale"
|
|
91
|
+
- "For security-sensitive dependencies, prefer short cache TTLs via run-id keys or disable caching entirely"
|
|
92
|
+
docs:
|
|
93
|
+
- url: "https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows"
|
|
94
|
+
label: "GitHub Docs — Caching dependencies to speed up workflows"
|
|
95
|
+
- url: "https://github.com/actions/cache/blob/main/tips-and-workarounds.md"
|
|
96
|
+
label: "actions/cache tips and workarounds"
|
|
97
|
+
- url: "https://github.com/actions/cache/issues/1380"
|
|
98
|
+
label: "actions/cache #1380 — cache never updates on key hit"
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
id: permissions-auth-059
|
|
2
|
+
title: 'GITHUB_TOKEN expires after 24 hours on self-hosted runners — long-running jobs fail with "GITHUB_TOKEN has expired"'
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- GITHUB_TOKEN
|
|
7
|
+
- token-expiry
|
|
8
|
+
- self-hosted-runner
|
|
9
|
+
- long-running-jobs
|
|
10
|
+
- 24-hour-limit
|
|
11
|
+
- authentication
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'GITHUB_TOKEN has expired'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'Unable to extend GITHUB_TOKEN expiration'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'token.*expired.*self.hosted'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Unable to extend GITHUB_TOKEN expiration time due to: GITHUB_TOKEN has expired."
|
|
21
|
+
- "Error: fatal: unable to access 'https://github.com/...': The requested URL returned error: 403"
|
|
22
|
+
root_cause: |
|
|
23
|
+
`GITHUB_TOKEN` is an installation access token issued at the START of each job. Its
|
|
24
|
+
lifetime is tied to the job execution, with a hard cap determined by the runner type:
|
|
25
|
+
|
|
26
|
+
- **GitHub-hosted runners**: `GITHUB_TOKEN` lives for up to 6 hours (matching the maximum
|
|
27
|
+
job execution time). On hosted runners this limit is never the issue because the job
|
|
28
|
+
itself can't run longer than 6 hours.
|
|
29
|
+
|
|
30
|
+
- **Self-hosted runners**: Jobs can run for up to 5 days, but `GITHUB_TOKEN` can only be
|
|
31
|
+
refreshed for up to **24 hours**. If a job on a self-hosted runner exceeds 24 hours of
|
|
32
|
+
runtime, any subsequent GitHub API call, `git push`, `gh` CLI invocation, or action that
|
|
33
|
+
uses `${{ github.token }}` or `${{ secrets.GITHUB_TOKEN }}` will fail with a 401/403
|
|
34
|
+
authentication error.
|
|
35
|
+
|
|
36
|
+
The specific error message is:
|
|
37
|
+
"Unable to extend GITHUB_TOKEN expiration time due to: GITHUB_TOKEN has expired."
|
|
38
|
+
|
|
39
|
+
This is particularly common in:
|
|
40
|
+
- Large test suites or build pipelines that process massive monorepos
|
|
41
|
+
- ML/data pipelines that process large datasets sequentially
|
|
42
|
+
- Long-running deployment or migration jobs that need GitHub API access at the end
|
|
43
|
+
- Jobs with retries that collectively exceed 24 hours
|
|
44
|
+
|
|
45
|
+
Note: `actions/create-github-app-token` GitHub App tokens have a similar but shorter
|
|
46
|
+
1-hour expiry — see permissions-auth-046 for that pattern.
|
|
47
|
+
fix: |
|
|
48
|
+
Several approaches, in order of recommendation:
|
|
49
|
+
|
|
50
|
+
1. **Split the job into smaller jobs** — Break the long-running job into multiple shorter
|
|
51
|
+
jobs connected by `needs:` dependencies. Each job gets its own fresh `GITHUB_TOKEN`.
|
|
52
|
+
|
|
53
|
+
2. **Use a GitHub App token** — Use `actions/create-github-app-token` to generate tokens
|
|
54
|
+
mid-job as needed, or generate a fresh token at the point in the job where you need it.
|
|
55
|
+
|
|
56
|
+
3. **Use a PAT (Personal Access Token)** — Store a long-lived PAT in repository or
|
|
57
|
+
organization secrets and use it in place of `GITHUB_TOKEN` for the API calls at the
|
|
58
|
+
end of the long-running job. PATs do not expire in 24 hours (they expire based on the
|
|
59
|
+
PAT expiration date you set). Drawback: PATs are tied to a specific user account.
|
|
60
|
+
|
|
61
|
+
4. **Use a machine user PAT** — Create a dedicated machine account and use its PAT.
|
|
62
|
+
This decouples the secret from any individual developer's account.
|
|
63
|
+
fix_code:
|
|
64
|
+
- language: yaml
|
|
65
|
+
label: 'Problem: long-running self-hosted job uses GITHUB_TOKEN after 24+ hours'
|
|
66
|
+
code: |
|
|
67
|
+
jobs:
|
|
68
|
+
long-build:
|
|
69
|
+
runs-on: [self-hosted, large-runner]
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/checkout@v4
|
|
72
|
+
- name: Run 26-hour data processing
|
|
73
|
+
run: ./scripts/process_all_data.sh # takes ~26 hours
|
|
74
|
+
|
|
75
|
+
# FAILS: GITHUB_TOKEN has expired by the time this step runs
|
|
76
|
+
- name: Upload results to GitHub
|
|
77
|
+
env:
|
|
78
|
+
GH_TOKEN: ${{ github.token }}
|
|
79
|
+
run: gh release upload v1.0 ./output/*.tar.gz
|
|
80
|
+
- language: yaml
|
|
81
|
+
label: 'Fix: split into jobs so each gets a fresh GITHUB_TOKEN'
|
|
82
|
+
code: |
|
|
83
|
+
jobs:
|
|
84
|
+
process-data:
|
|
85
|
+
runs-on: [self-hosted, large-runner]
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/checkout@v4
|
|
88
|
+
- run: ./scripts/process_all_data.sh
|
|
89
|
+
- uses: actions/upload-artifact@v4
|
|
90
|
+
with:
|
|
91
|
+
name: output-files
|
|
92
|
+
path: ./output/
|
|
93
|
+
|
|
94
|
+
upload-release:
|
|
95
|
+
needs: process-data
|
|
96
|
+
runs-on: ubuntu-latest # hosted runner with fresh token
|
|
97
|
+
permissions:
|
|
98
|
+
contents: write
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/download-artifact@v4
|
|
101
|
+
with:
|
|
102
|
+
name: output-files
|
|
103
|
+
path: ./output/
|
|
104
|
+
# GITHUB_TOKEN is fresh — just issued for this new job
|
|
105
|
+
- name: Upload to release
|
|
106
|
+
env:
|
|
107
|
+
GH_TOKEN: ${{ github.token }}
|
|
108
|
+
run: gh release upload v1.0 ./output/*.tar.gz
|
|
109
|
+
- language: yaml
|
|
110
|
+
label: 'Fix: use stored PAT when splitting jobs is not feasible'
|
|
111
|
+
code: |
|
|
112
|
+
jobs:
|
|
113
|
+
long-build:
|
|
114
|
+
runs-on: [self-hosted, large-runner]
|
|
115
|
+
steps:
|
|
116
|
+
- uses: actions/checkout@v4
|
|
117
|
+
- run: ./scripts/process_all_data.sh
|
|
118
|
+
|
|
119
|
+
# Use a PAT stored in secrets — does not expire in 24 hours
|
|
120
|
+
- name: Upload results (using PAT)
|
|
121
|
+
env:
|
|
122
|
+
GH_TOKEN: ${{ secrets.MACHINE_USER_PAT }} # PAT, not github.token
|
|
123
|
+
run: gh release upload v1.0 ./output/*.tar.gz
|
|
124
|
+
prevention:
|
|
125
|
+
- "Design self-hosted runner jobs to complete within 24 hours, or split them into smaller jobs connected by `needs:`."
|
|
126
|
+
- "If a job genuinely requires >24 hours of runtime, use a PAT or GitHub App token for API calls instead of `GITHUB_TOKEN`."
|
|
127
|
+
- "Add a monitoring step that logs elapsed job time — alert if a job approaches 20+ hours."
|
|
128
|
+
- "Avoid using `GITHUB_TOKEN` for API calls near the end of jobs that are known to run close to the 24-hour limit."
|
|
129
|
+
- "For GitHub-hosted runners this is not an issue — the job execution limit (6 hours) is shorter than the token lifetime."
|
|
130
|
+
docs:
|
|
131
|
+
- url: 'https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#about-the-github_token-secret'
|
|
132
|
+
label: 'GitHub Docs: GITHUB_TOKEN — effective maximum lifetime'
|
|
133
|
+
- url: 'https://stackoverflow.com/questions/75602556/how-can-i-use-a-github-token-for-more-than-24-hours'
|
|
134
|
+
label: 'Stack Overflow: How to use GITHUB_TOKEN for more than 24 hours'
|
|
135
|
+
- url: 'https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#usage-limits'
|
|
136
|
+
label: 'GitHub Docs: Self-hosted runner usage limits (5-day job limit)'
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
id: permissions-auth-060
|
|
2
|
+
title: 'Fork Pull Request Workflows Cannot Access Repository Secrets — Secrets Context Is Empty'
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- fork
|
|
7
|
+
- pull-request
|
|
8
|
+
- secrets
|
|
9
|
+
- security
|
|
10
|
+
- workflow-permissions
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'secrets\.[\w_]+.*empty|secrets context.*fork'
|
|
13
|
+
flags: 'i'
|
|
14
|
+
error_messages:
|
|
15
|
+
- "Error: Input required and not supplied: token"
|
|
16
|
+
- "HttpError: Bad credentials"
|
|
17
|
+
- "Error: Resource not accessible by integration"
|
|
18
|
+
root_cause: |
|
|
19
|
+
GitHub's security model prevents workflows triggered by `pull_request` events from
|
|
20
|
+
fork repositories from accessing repository secrets. This applies to ALL non-Dependabot
|
|
21
|
+
external contributor forks — not just Dependabot.
|
|
22
|
+
|
|
23
|
+
When a fork PR workflow runs:
|
|
24
|
+
- All `secrets.*` values resolve to empty string `''`
|
|
25
|
+
- `secrets.GITHUB_TOKEN` is replaced with a read-only token scoped only to the fork
|
|
26
|
+
repository (cannot write to the upstream repo, cannot access packages, etc.)
|
|
27
|
+
- Environment secrets are also unavailable
|
|
28
|
+
- Fine-grained PATs stored as secrets are unavailable
|
|
29
|
+
|
|
30
|
+
This is intentional — allowing forks to read secrets would enable malicious PRs to
|
|
31
|
+
exfiltrate credentials.
|
|
32
|
+
|
|
33
|
+
Common failure patterns:
|
|
34
|
+
- `actions/setup-node` with `registry-url` + `NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}` → 401 Unauthorized
|
|
35
|
+
- `docker/login-action` with `${{ secrets.DOCKERHUB_TOKEN }}` → login fails silently, image push 403
|
|
36
|
+
- `aws-actions/configure-aws-credentials` with role ARN from secrets → empty role, OIDC fallback
|
|
37
|
+
- Custom actions reading a `token:` input wired to `${{ secrets.MY_TOKEN }}` → action receives ''
|
|
38
|
+
and may throw "Input required and not supplied: token"
|
|
39
|
+
|
|
40
|
+
The fork PR restriction also applies to `workflow_dispatch` when triggered from a fork,
|
|
41
|
+
and to `check_suite`/`check_run` events from fork PRs.
|
|
42
|
+
|
|
43
|
+
Note: `pull_request_target` DOES have access to secrets because it runs in the context
|
|
44
|
+
of the BASE repository — but this introduces a different security risk (running untrusted
|
|
45
|
+
code with secret access) that requires careful mitigation.
|
|
46
|
+
fix: |
|
|
47
|
+
For CI checks that don't need secrets (lint, unit tests, build validation), no change
|
|
48
|
+
is needed — the read-only GITHUB_TOKEN is sufficient.
|
|
49
|
+
|
|
50
|
+
For steps that require secrets:
|
|
51
|
+
|
|
52
|
+
1. Gate secret-requiring steps on `github.event.pull_request.head.repo.fork == false`
|
|
53
|
+
to skip them on fork PRs gracefully.
|
|
54
|
+
2. Use `pull_request_target` + a separate privileged job for publishing/deploying,
|
|
55
|
+
but ALWAYS check out from `github.event.pull_request.head.sha` explicitly and
|
|
56
|
+
never run untrusted code in the same job.
|
|
57
|
+
3. For package publishing, only publish from base branch pushes (not from PRs at all).
|
|
58
|
+
fix_code:
|
|
59
|
+
- language: yaml
|
|
60
|
+
label: "Skip secret-requiring steps on fork PRs gracefully"
|
|
61
|
+
code: |
|
|
62
|
+
jobs:
|
|
63
|
+
build:
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
steps:
|
|
66
|
+
- uses: actions/checkout@v4
|
|
67
|
+
|
|
68
|
+
- name: Run tests (works on fork PRs)
|
|
69
|
+
run: npm test
|
|
70
|
+
|
|
71
|
+
- name: Publish coverage report (skip on fork PRs)
|
|
72
|
+
if: github.event.pull_request.head.repo.fork == false
|
|
73
|
+
env:
|
|
74
|
+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
75
|
+
run: npx codecov
|
|
76
|
+
- language: yaml
|
|
77
|
+
label: "Split fork-safe CI from privileged deployment using workflow_run"
|
|
78
|
+
code: |
|
|
79
|
+
# workflow: ci.yml — runs on all PRs including forks (no secrets needed)
|
|
80
|
+
on:
|
|
81
|
+
pull_request:
|
|
82
|
+
jobs:
|
|
83
|
+
test:
|
|
84
|
+
runs-on: ubuntu-latest
|
|
85
|
+
steps:
|
|
86
|
+
- uses: actions/checkout@v4
|
|
87
|
+
- run: npm ci && npm test
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
# workflow: publish-coverage.yml — runs after ci.yml completes (has secrets)
|
|
91
|
+
on:
|
|
92
|
+
workflow_run:
|
|
93
|
+
workflows: ['CI']
|
|
94
|
+
types: [completed]
|
|
95
|
+
jobs:
|
|
96
|
+
coverage:
|
|
97
|
+
if: github.event.workflow_run.conclusion == 'success'
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
steps:
|
|
100
|
+
- name: Publish coverage
|
|
101
|
+
env:
|
|
102
|
+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
103
|
+
run: echo "Publishing coverage for run ${{ github.event.workflow_run.id }}"
|
|
104
|
+
prevention:
|
|
105
|
+
- "Design CI workflows to not require secrets for the build/test steps — secrets should only be needed for publish/deploy"
|
|
106
|
+
- "Check `github.event.pull_request.head.repo.fork` before any step that uses secrets to provide a clear skip message"
|
|
107
|
+
- "Do not use `pull_request_target` as a shortcut to get secrets — it runs untrusted fork code with base repo privileges, creating a severe injection risk"
|
|
108
|
+
- "Use `workflow_run` to chain a privileged follow-up workflow that runs in base repo context after fork CI succeeds"
|
|
109
|
+
docs:
|
|
110
|
+
- url: "https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections"
|
|
111
|
+
label: "GitHub Docs — Security hardening: fork PR and secret access"
|
|
112
|
+
- url: "https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request"
|
|
113
|
+
label: "GitHub Docs — pull_request event: fork limitations"
|
|
114
|
+
- url: "https://securitylab.github.com/research/github-actions-preventing-pwn-requests/"
|
|
115
|
+
label: "GitHub Security Lab — Preventing pwn requests (pull_request_target risks)"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
id: runner-environment-172
|
|
2
|
+
title: 'Homebrew 4.x triggers auto-update on every brew install, adding 1-3 minutes to macOS CI'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- macos
|
|
7
|
+
- homebrew
|
|
8
|
+
- brew
|
|
9
|
+
- slow-ci
|
|
10
|
+
- performance
|
|
11
|
+
- HOMEBREW_NO_AUTO_UPDATE
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: '==> Auto-updated Homebrew!|Updating Homebrew\.\.\.|Auto-update took'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'Fetching https://formulae\.brew\.sh/api/(formula|cask)\.jws\.json'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "==> Auto-updated Homebrew!"
|
|
19
|
+
- "Updating Homebrew..."
|
|
20
|
+
- "Fetching https://formulae.brew.sh/api/formula.jws.json"
|
|
21
|
+
- "This operation has taken more than 5 minutes"
|
|
22
|
+
root_cause: |
|
|
23
|
+
Homebrew 4.0 (released February 2023) replaced its previous approach of cloning the
|
|
24
|
+
homebrew-core and homebrew-cask Git repositories with downloading pre-computed JSON API
|
|
25
|
+
responses from formulae.brew.sh. While this reduced the on-disk size of the Homebrew
|
|
26
|
+
installation, Homebrew retained its auto-update behavior: by default, any brew install,
|
|
27
|
+
brew upgrade, or brew reinstall command triggers an auto-update check if the local
|
|
28
|
+
formula cache is older than HOMEBREW_AUTO_UPDATE_SECS (default: 300 seconds = 5 minutes).
|
|
29
|
+
|
|
30
|
+
On GitHub-hosted macOS runners, every fresh runner starts with a Homebrew installation
|
|
31
|
+
that has not been updated recently, so the first brew install in any job always triggers
|
|
32
|
+
a full API fetch from formulae.brew.sh. This fetch downloads large JSON manifests for
|
|
33
|
+
formula and cask databases and typically adds 1-3 minutes to the job. For workflows with
|
|
34
|
+
multiple parallel matrix jobs or multiple brew install calls, this overhead compounds.
|
|
35
|
+
|
|
36
|
+
Homebrew 4.x also added HOMEBREW_AUTO_UPDATE_SECS and related env vars to control this
|
|
37
|
+
behavior, but the default settings cause every fresh runner to update on first use.
|
|
38
|
+
fix: |
|
|
39
|
+
Set HOMEBREW_NO_AUTO_UPDATE=1 as a workflow environment variable to disable automatic
|
|
40
|
+
updates entirely. The macOS runner image ships a recent-enough Homebrew installation for
|
|
41
|
+
most use cases. If you need the absolute latest formula versions for a specific tool,
|
|
42
|
+
run a single explicit brew update at the start of the job.
|
|
43
|
+
|
|
44
|
+
Also set HOMEBREW_NO_INSTALL_CLEANUP=1 to prevent cleanup passes that extend install time.
|
|
45
|
+
fix_code:
|
|
46
|
+
- language: yaml
|
|
47
|
+
label: 'Disable Homebrew auto-update at workflow level (recommended)'
|
|
48
|
+
code: |
|
|
49
|
+
env:
|
|
50
|
+
HOMEBREW_NO_AUTO_UPDATE: '1'
|
|
51
|
+
HOMEBREW_NO_INSTALL_CLEANUP: '1'
|
|
52
|
+
|
|
53
|
+
jobs:
|
|
54
|
+
build:
|
|
55
|
+
runs-on: macos-latest
|
|
56
|
+
steps:
|
|
57
|
+
- name: Install build dependencies
|
|
58
|
+
run: brew install ninja cmake
|
|
59
|
+
- language: yaml
|
|
60
|
+
label: 'Run one explicit update then suppress auto-update per job'
|
|
61
|
+
code: |
|
|
62
|
+
jobs:
|
|
63
|
+
build:
|
|
64
|
+
runs-on: macos-latest
|
|
65
|
+
env:
|
|
66
|
+
HOMEBREW_NO_AUTO_UPDATE: '1'
|
|
67
|
+
HOMEBREW_NO_INSTALL_CLEANUP: '1'
|
|
68
|
+
steps:
|
|
69
|
+
- name: Update Homebrew (once, explicit)
|
|
70
|
+
run: brew update
|
|
71
|
+
- name: Install tools
|
|
72
|
+
run: brew install ninja cmake
|
|
73
|
+
prevention:
|
|
74
|
+
- 'Set HOMEBREW_NO_AUTO_UPDATE=1 and HOMEBREW_NO_INSTALL_CLEANUP=1 as top-level workflow env vars'
|
|
75
|
+
- 'Use actions/setup-* official actions instead of brew install when available (setup-python, setup-node, etc.)'
|
|
76
|
+
- 'Cache brew downloads using actions/cache with a key derived from a Brewfile or explicit package list'
|
|
77
|
+
- 'Set HOMEBREW_AUTO_UPDATE_SECS=86400 to limit auto-updates to at most once per day if you need periodic updates'
|
|
78
|
+
docs:
|
|
79
|
+
- url: 'https://docs.brew.sh/Manpage#environment'
|
|
80
|
+
label: 'Homebrew environment variables — HOMEBREW_NO_AUTO_UPDATE'
|
|
81
|
+
- url: 'https://brew.sh/2023/02/16/homebrew-4.0.0/'
|
|
82
|
+
label: 'Homebrew 4.0.0 release notes — JSON API migration'
|
|
83
|
+
- url: 'https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md'
|
|
84
|
+
label: 'macOS 15 runner image — preinstalled Homebrew version'
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
id: runner-environment-171
|
|
2
|
+
title: 'Python 3.12 removes deprecated ast.Str, ast.Num, ast.NameConstant on ubuntu-24.04 — older linters crash'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- ubuntu-24.04
|
|
7
|
+
- python
|
|
8
|
+
- python-3.12
|
|
9
|
+
- ast
|
|
10
|
+
- linting
|
|
11
|
+
- pylint
|
|
12
|
+
- bandit
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'AttributeError: module ''ast'' has no attribute ''(Str|Num|NameConstant|Bytes)'''
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'ImportError.*pylint|cannot import name.*astroid|AttributeError.*ast\.(Str|Num)'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
error_messages:
|
|
19
|
+
- "AttributeError: module 'ast' has no attribute 'Str'"
|
|
20
|
+
- "AttributeError: module 'ast' has no attribute 'Num'"
|
|
21
|
+
- "AttributeError: module 'ast' has no attribute 'NameConstant'"
|
|
22
|
+
- "AttributeError: module 'ast' has no attribute 'Bytes'"
|
|
23
|
+
root_cause: |
|
|
24
|
+
Ubuntu 24.04 ships Python 3.12 as the default system Python (/usr/bin/python3).
|
|
25
|
+
Python 3.12 permanently removed several deprecated AST node types that were soft-deprecated
|
|
26
|
+
since Python 3.8 and slated for removal in 3.12: ast.Str, ast.Num, ast.NameConstant,
|
|
27
|
+
ast.Bytes, and the legacy constant-value wrapper nodes.
|
|
28
|
+
|
|
29
|
+
Many popular static analysis tools directly referenced these internal AST nodes for
|
|
30
|
+
backward compatibility with Python 2 and early Python 3 code. Affected tools include:
|
|
31
|
+
- pylint < 3.0 (uses astroid which references ast.Str/ast.Num for constant folding)
|
|
32
|
+
- astroid < 3.0 (core dependency of pylint; crash on import with Python 3.12)
|
|
33
|
+
- bandit < 1.8.0 (security linter; uses ast.Str for string literal detection)
|
|
34
|
+
- flake8-bugbear < 23.x (some plugin versions reference ast.Num directly)
|
|
35
|
+
- pyflakes < 3.0 (uses ast.Str for format string analysis)
|
|
36
|
+
|
|
37
|
+
Workflows that install these linters without pinning minimum versions fail immediately
|
|
38
|
+
when the runner migrates from ubuntu-22.04 (Python 3.10) to ubuntu-24.04 (Python 3.12).
|
|
39
|
+
The error surfaces as an AttributeError during tool import, before any code is analyzed.
|
|
40
|
+
fix: |
|
|
41
|
+
Upgrade the affected analysis tools to Python 3.12-compatible versions:
|
|
42
|
+
- pylint: upgrade to 3.0+ (requires astroid >= 3.0 simultaneously)
|
|
43
|
+
- bandit: upgrade to 1.8.0+
|
|
44
|
+
- flake8: upgrade to 7.0+ with compatible plugin versions
|
|
45
|
+
|
|
46
|
+
If an immediate upgrade is not feasible, pin the runner to ubuntu-22.04 (Python 3.10)
|
|
47
|
+
temporarily while the upgrade is planned. Ubuntu 22.04 runner support continues until
|
|
48
|
+
at least April 2027.
|
|
49
|
+
fix_code:
|
|
50
|
+
- language: yaml
|
|
51
|
+
label: 'Upgrade linting tools to Python 3.12-compatible versions'
|
|
52
|
+
code: |
|
|
53
|
+
- name: Install Python linters (3.12 compatible)
|
|
54
|
+
run: pip install 'pylint>=3.0' 'astroid>=3.0' 'bandit>=1.8.0' 'flake8>=7.0'
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: 'Temporary workaround — pin to ubuntu-22.04 (Python 3.10)'
|
|
57
|
+
code: |
|
|
58
|
+
jobs:
|
|
59
|
+
lint:
|
|
60
|
+
runs-on: ubuntu-22.04 # Python 3.10 — avoids ast.Str removal in 3.12
|
|
61
|
+
prevention:
|
|
62
|
+
- 'Pin minimum versions for linting tools in requirements-dev.txt or pyproject.toml'
|
|
63
|
+
- 'Use actions/setup-python to control the Python version explicitly rather than relying on system Python'
|
|
64
|
+
- 'Test your CI toolchain against Python 3.12 before migrating to ubuntu-24.04'
|
|
65
|
+
- 'Review the Python 3.12 changelog for all removed deprecated features before upgrading'
|
|
66
|
+
docs:
|
|
67
|
+
- url: 'https://docs.python.org/3.12/whatsnew/3.12.html#removed'
|
|
68
|
+
label: 'Python 3.12 Removed Features'
|
|
69
|
+
- url: 'https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md'
|
|
70
|
+
label: 'Ubuntu 24.04 runner image software listing'
|
|
71
|
+
- url: 'https://pylint.readthedocs.io/en/stable/whatsnew/3.0/summary.html'
|
|
72
|
+
label: 'Pylint 3.0 migration and Python 3.12 compatibility notes'
|
|
73
|
+
- url: 'https://github.com/PyCQA/bandit/releases/tag/1.8.0'
|
|
74
|
+
label: 'bandit 1.8.0 release notes — Python 3.12 AST compatibility'
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
id: runner-environment-170
|
|
2
|
+
title: 'windows-latest Migration to Windows Server 2025 Removes MSVC v142 (VS 2019) Toolchain'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- windows
|
|
7
|
+
- msvc
|
|
8
|
+
- cmake
|
|
9
|
+
- msbuild
|
|
10
|
+
- toolchain
|
|
11
|
+
- migration
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'MSB8020.*v142.*cannot be found'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'build tools for v142.*Platform Toolset.*v142.*cannot be found'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'CMAKE_GENERATOR_TOOLSET.*v142.*not found'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- "MSBUILD : error MSB8020: The build tools for v142 (Platform Toolset = 'v142') cannot be found."
|
|
21
|
+
- "CMake Error: The CMAKE_CXX_COMPILER: cl.exe is not able to compile a simple test program."
|
|
22
|
+
- "error MSB8020: The build tools for v142 (Platform Toolset = 'v142') cannot be found."
|
|
23
|
+
root_cause: |
|
|
24
|
+
Starting November 2024, `windows-latest` was migrated to Windows Server 2025 with
|
|
25
|
+
Visual Studio 2022 17.12 as the default build environment. Windows Server 2025 runner
|
|
26
|
+
images do not include the Visual Studio 2019 build tools (MSVC v142 / toolset 14.2).
|
|
27
|
+
|
|
28
|
+
Projects that explicitly request the VS 2019 toolchain via any of these mechanisms fail:
|
|
29
|
+
- MSBuild `<PlatformToolset>v142</PlatformToolset>` in .vcxproj files
|
|
30
|
+
- CMake `-T v142` flag or `set(CMAKE_GENERATOR_TOOLSET v142)` in CMakeLists.txt
|
|
31
|
+
- MSBuild command-line flag `/p:PlatformToolset=v142`
|
|
32
|
+
- Visual Studio solution files pinned to VS 2019 (toolset version 142)
|
|
33
|
+
|
|
34
|
+
Windows Server 2022 runners (`windows-2022`) continue to offer both VS 2019
|
|
35
|
+
(v142) and VS 2022 (v143) toolchains in parallel. After the `windows-latest`
|
|
36
|
+
migration, workflows that did not pin to `windows-2022` and relied on v142
|
|
37
|
+
break silently on runners already migrated while appearing to work in older
|
|
38
|
+
runner pools.
|
|
39
|
+
|
|
40
|
+
The migration timeline:
|
|
41
|
+
- Nov 2024: windows-latest begins pointing to Windows Server 2025 (partial rollout)
|
|
42
|
+
- Early 2025: Full rollout; all new `windows-latest` queue jobs use WS 2025
|
|
43
|
+
|
|
44
|
+
Reference: https://github.com/actions/runner-images/issues/10751
|
|
45
|
+
fix: |
|
|
46
|
+
Choose one of the following approaches:
|
|
47
|
+
|
|
48
|
+
1. Upgrade to the v143 (VS 2022) toolchain — recommended for long-term compatibility.
|
|
49
|
+
2. Pin to `windows-2022` runner if you cannot migrate immediately.
|
|
50
|
+
3. Install the VS 2019 build tools component manually (slow, increases job time).
|
|
51
|
+
fix_code:
|
|
52
|
+
- language: yaml
|
|
53
|
+
label: "Option A: upgrade toolchain to v143 (VS 2022) in CMakeLists.txt"
|
|
54
|
+
code: |
|
|
55
|
+
# In CMakeLists.txt — remove explicit toolset pinning
|
|
56
|
+
# cmake_minimum_required(VERSION 3.20)
|
|
57
|
+
# project(MyProject)
|
|
58
|
+
# Previously had: set(CMAKE_GENERATOR_TOOLSET "v142")
|
|
59
|
+
# Remove or update to:
|
|
60
|
+
# set(CMAKE_GENERATOR_TOOLSET "v143") # or omit to use default
|
|
61
|
+
- language: yaml
|
|
62
|
+
label: "Option B: pin to windows-2022 to keep v142 support"
|
|
63
|
+
code: |
|
|
64
|
+
jobs:
|
|
65
|
+
build:
|
|
66
|
+
runs-on: windows-2022 # has both v142 and v143 available
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v4
|
|
69
|
+
- name: Build with CMake
|
|
70
|
+
run: cmake -B build -T v142 && cmake --build build
|
|
71
|
+
- language: yaml
|
|
72
|
+
label: "Option C: install VS 2019 build tools on windows-latest (slow)"
|
|
73
|
+
code: |
|
|
74
|
+
jobs:
|
|
75
|
+
build:
|
|
76
|
+
runs-on: windows-latest
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/checkout@v4
|
|
79
|
+
- name: Install VS 2019 Build Tools
|
|
80
|
+
run: |
|
|
81
|
+
choco install visualstudio2019buildtools --package-parameters "--add Microsoft.VisualStudio.Component.VC.v142.x86.x64" -y
|
|
82
|
+
prevention:
|
|
83
|
+
- "Audit all .vcxproj and CMakeLists.txt files for explicit v142 toolset references before migrating to windows-latest"
|
|
84
|
+
- "Pin to `windows-2022` in CI to preserve VS 2019 toolchain availability while planning an upgrade"
|
|
85
|
+
- "Watch https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md for current pre-installed toolchain versions"
|
|
86
|
+
- "Use `vswhere` in a pre-build step to detect installed VS components and fail fast with a descriptive error"
|
|
87
|
+
docs:
|
|
88
|
+
- url: "https://github.com/actions/runner-images/issues/10751"
|
|
89
|
+
label: "runner-images #10751 — windows-latest migration to Windows Server 2025"
|
|
90
|
+
- url: "https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md"
|
|
91
|
+
label: "Windows Server 2025 runner image README — pre-installed software"
|
|
92
|
+
- url: "https://learn.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-170"
|
|
93
|
+
label: "Microsoft Docs — CMake toolset configuration"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
id: runner-environment-173
|
|
2
|
+
title: 'actions/setup-go with EOL Go versions (1.21, 1.22) not in runner tool cache — slow downloads or failures'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- setup-go
|
|
7
|
+
- go
|
|
8
|
+
- golang
|
|
9
|
+
- eol
|
|
10
|
+
- tool-cache
|
|
11
|
+
- go-version
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'go version [\d.]+ is not in the tool-cache.*Falling back to download|go\d+\.\d+\.?\d*.*not found.*tool.cache'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'Downloading go[\d.]+.*from https://dl\.google\.com/go'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'Failed to download Go from|Unable to find Go version.*in cache'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- "go version 1.21.x is not in the tool-cache. Falling back to downloading from https://dl.google.com/go"
|
|
21
|
+
- "go version 1.22.x is not in the tool-cache. Falling back to downloading from https://dl.google.com/go"
|
|
22
|
+
- "Failed to download Go from https://dl.google.com/go"
|
|
23
|
+
- "Unable to find Go version 1.21 in cache"
|
|
24
|
+
root_cause: |
|
|
25
|
+
GitHub removes end-of-life Go versions from the runner image tool cache after their
|
|
26
|
+
official support window closes. Go follows a two-release support policy: only the two
|
|
27
|
+
most recent major releases receive security updates. Once a version is EOL, it is no
|
|
28
|
+
longer pre-installed on new runner images.
|
|
29
|
+
|
|
30
|
+
Affected versions as of 2025-2026:
|
|
31
|
+
- Go 1.21 — EOL February 6, 2024 (removed from runner tool cache ~Q2 2024)
|
|
32
|
+
- Go 1.22 — EOL August 6, 2025 (removed from runner tool cache ~Q4 2025)
|
|
33
|
+
|
|
34
|
+
When actions/setup-go requests a version not in the tool cache, it falls back to
|
|
35
|
+
downloading the Go toolchain from dl.google.com/go. This fallback:
|
|
36
|
+
1. Adds 30-90 seconds of download time per job (depending on network speed)
|
|
37
|
+
2. Fails entirely in self-hosted runners without internet access or strict outbound
|
|
38
|
+
firewall rules blocking dl.google.com
|
|
39
|
+
3. May fail intermittently if the Google CDN experiences transient issues
|
|
40
|
+
|
|
41
|
+
Workflows with many parallel matrix jobs (e.g., matrix of Go versions or OS targets)
|
|
42
|
+
multiply this overhead: 20 parallel jobs each downloading Go = 20 concurrent CDN requests.
|
|
43
|
+
fix: |
|
|
44
|
+
Upgrade to a currently-supported Go version. As of 2025-2026, the two supported releases
|
|
45
|
+
are Go 1.23 and Go 1.24. Both are pre-cached in the runner tool cache and resolve
|
|
46
|
+
instantly without any network download.
|
|
47
|
+
|
|
48
|
+
Use the go-version-file input to read the version from go.mod, ensuring your CI always
|
|
49
|
+
matches your project's declared Go version.
|
|
50
|
+
fix_code:
|
|
51
|
+
- language: yaml
|
|
52
|
+
label: 'Pin to a supported Go version (no tool-cache miss)'
|
|
53
|
+
code: |
|
|
54
|
+
- name: Set up Go
|
|
55
|
+
uses: actions/setup-go@v5
|
|
56
|
+
with:
|
|
57
|
+
go-version: '1.24' # or '1.23' — both are in the runner tool cache
|
|
58
|
+
cache: true
|
|
59
|
+
- language: yaml
|
|
60
|
+
label: 'Use go-version-file to read version from go.mod'
|
|
61
|
+
code: |
|
|
62
|
+
- name: Set up Go
|
|
63
|
+
uses: actions/setup-go@v5
|
|
64
|
+
with:
|
|
65
|
+
go-version-file: 'go.mod' # reads `go 1.24` directive from go.mod
|
|
66
|
+
cache: true
|
|
67
|
+
prevention:
|
|
68
|
+
- 'Keep go-version in your workflows aligned with the go directive in go.mod'
|
|
69
|
+
- 'Upgrade Go versions proactively before EOL — the Go team announces EOL dates 6 months in advance'
|
|
70
|
+
- 'Use go-version-file: go.mod so your CI automatically follows your module declaration'
|
|
71
|
+
- 'Monitor https://go.dev/doc/devel/release for maintenance status of Go releases'
|
|
72
|
+
docs:
|
|
73
|
+
- url: 'https://go.dev/doc/devel/release'
|
|
74
|
+
label: 'Go release history and maintenance policy'
|
|
75
|
+
- url: 'https://github.com/actions/setup-go'
|
|
76
|
+
label: 'actions/setup-go — go-version and go-version-file inputs'
|
|
77
|
+
- url: 'https://github.com/actions/runner-images'
|
|
78
|
+
label: 'GitHub runner images — preinstalled tool versions'
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
id: silent-failures-091
|
|
2
|
+
title: '`github.ref_name` Returns Ephemeral Merge Ref (`123/merge`) on `pull_request` Trigger, Not Branch Name'
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- github-context
|
|
7
|
+
- ref-name
|
|
8
|
+
- pull-request
|
|
9
|
+
- docker
|
|
10
|
+
- branch-name
|
|
11
|
+
- deployment
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: '\d+/merge'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "invalid reference format"
|
|
17
|
+
- "invalid tag format"
|
|
18
|
+
root_cause: |
|
|
19
|
+
When a workflow is triggered by a `pull_request` event, GitHub creates an ephemeral
|
|
20
|
+
merge commit that merges the PR head into the base branch. The context variables for
|
|
21
|
+
this run reflect the merge ref, not the source branch:
|
|
22
|
+
|
|
23
|
+
- `github.ref` = `refs/pull/123/merge`
|
|
24
|
+
- `github.ref_name` = `123/merge`
|
|
25
|
+
- `github.sha` = SHA of the ephemeral merge commit
|
|
26
|
+
- `github.head_ref` = `feature-branch-name` (the actual PR branch)
|
|
27
|
+
- `github.base_ref` = `main` (the target branch)
|
|
28
|
+
|
|
29
|
+
Using `github.ref_name` in contexts that expect a branch name silently produces
|
|
30
|
+
the merge ref string `123/merge` instead:
|
|
31
|
+
|
|
32
|
+
- **Docker image tags**: `docker.io/myapp:123/merge` — Docker rejects the forward slash
|
|
33
|
+
as an invalid tag character (`invalid reference format` error), OR worse, interprets
|
|
34
|
+
`123` as a registry name.
|
|
35
|
+
- **Deployment environment names**: The environment is created as `123/merge` rather
|
|
36
|
+
than the branch name, causing routing rules to miss the deployment.
|
|
37
|
+
- **Branch-based conditional logic**: `if: github.ref_name == 'feature-foo'` always
|
|
38
|
+
evaluates to false on pull_request events.
|
|
39
|
+
- **Artifact naming**: Artifacts uploaded with the ref_name contain forward slashes,
|
|
40
|
+
causing path traversal issues on some download handlers.
|
|
41
|
+
|
|
42
|
+
`github.head_ref` is only populated for `pull_request` and `pull_request_target`
|
|
43
|
+
events, making branch detection logic that works on push but silently fails on PR
|
|
44
|
+
events harder to diagnose.
|
|
45
|
+
fix: |
|
|
46
|
+
Use the correct context variable for each event type:
|
|
47
|
+
|
|
48
|
+
- For the PR's source branch name: `github.head_ref` (only set on pull_request events)
|
|
49
|
+
- For push events' branch name: `github.ref_name`
|
|
50
|
+
- For a branch name that works across both event types, use a conditional expression.
|
|
51
|
+
fix_code:
|
|
52
|
+
- language: yaml
|
|
53
|
+
label: "Unified branch name across push and pull_request events"
|
|
54
|
+
code: |
|
|
55
|
+
jobs:
|
|
56
|
+
build:
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
steps:
|
|
59
|
+
- name: Determine branch name
|
|
60
|
+
id: branch
|
|
61
|
+
run: |
|
|
62
|
+
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
63
|
+
echo "name=${{ github.head_ref }}" >> "$GITHUB_OUTPUT"
|
|
64
|
+
else
|
|
65
|
+
echo "name=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
- name: Build and tag Docker image
|
|
69
|
+
run: |
|
|
70
|
+
# Sanitize slashes for use in image tags
|
|
71
|
+
TAG=$(echo "${{ steps.branch.outputs.name }}" | tr '/' '-')
|
|
72
|
+
docker build -t "myapp:${TAG}" .
|
|
73
|
+
- language: yaml
|
|
74
|
+
label: "Expression-based branch name (no shell step needed)"
|
|
75
|
+
code: |
|
|
76
|
+
env:
|
|
77
|
+
BRANCH_NAME: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}
|
|
78
|
+
|
|
79
|
+
jobs:
|
|
80
|
+
deploy:
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
environment: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}
|
|
83
|
+
steps:
|
|
84
|
+
- run: echo "Deploying branch ${{ env.BRANCH_NAME }}"
|
|
85
|
+
prevention:
|
|
86
|
+
- "Never use `github.ref_name` directly in Docker image tags — always sanitize forward slashes with `tr '/' '-'` or similar"
|
|
87
|
+
- "Test branch-detection expressions with both `push` and `pull_request` triggers in a dry-run workflow"
|
|
88
|
+
- "Use `github.head_ref` for PR branch name and `github.ref_name` for push branch name — they are NOT interchangeable"
|
|
89
|
+
- "Add an explicit check: if `github.ref_name` contains a `/`, the workflow is running on a pull_request or merge ref, not a regular branch"
|
|
90
|
+
docs:
|
|
91
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#github-context"
|
|
92
|
+
label: "GitHub Docs — github context reference"
|
|
93
|
+
- url: "https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request"
|
|
94
|
+
label: "GitHub Docs — pull_request event"
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
id: triggers-067
|
|
2
|
+
title: 'workflow_run types:[completed] fires prematurely with conclusion:null before referenced workflow finishes'
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- workflow_run
|
|
7
|
+
- conclusion
|
|
8
|
+
- null
|
|
9
|
+
- premature-trigger
|
|
10
|
+
- race-condition
|
|
11
|
+
- completed
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'workflow_run\.conclusion.*null'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'github\.event\.workflow_run\.conclusion'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'conclusion.*randomly.*fail'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- "github.event.workflow_run.conclusion returned null — job skipped unexpectedly"
|
|
21
|
+
- "Unable to extend GITHUB_TOKEN expiration time due to: GITHUB_TOKEN has expired."
|
|
22
|
+
root_cause: |
|
|
23
|
+
The `workflow_run` event with `types: [completed]` is documented to fire only after the
|
|
24
|
+
referenced workflow finishes running. However, a known race condition in GitHub Actions'
|
|
25
|
+
event delivery (tracked in actions/runner#4035) causes the event to fire a second time
|
|
26
|
+
BEFORE the workflow has fully completed — specifically before GitHub has written the final
|
|
27
|
+
`conclusion` value to the workflow run record.
|
|
28
|
+
|
|
29
|
+
When this premature trigger fires, `github.event.workflow_run.conclusion` evaluates to
|
|
30
|
+
`null` (or an empty string `""`). Any job guarded with:
|
|
31
|
+
|
|
32
|
+
if: github.event.workflow_run.conclusion == 'success'
|
|
33
|
+
|
|
34
|
+
will evaluate to `false` and be skipped, producing a confusing pattern where the
|
|
35
|
+
downstream workflow runs twice — once skipped, once (correctly) executed.
|
|
36
|
+
|
|
37
|
+
GitHub Support confirmed this as a platform-side delay: the `conclusion` field remains
|
|
38
|
+
`null` while GitHub's backend is still updating the workflow run record. The event
|
|
39
|
+
is dispatched before the record is fully consistent.
|
|
40
|
+
|
|
41
|
+
This affects all workflows that use `on.workflow_run` with `types: [completed]` and
|
|
42
|
+
check `github.event.workflow_run.conclusion` to gate job execution.
|
|
43
|
+
fix: |
|
|
44
|
+
Two mitigations are available:
|
|
45
|
+
|
|
46
|
+
1. **Guard with `conclusion != ''`** — Add a condition that explicitly rejects null/empty
|
|
47
|
+
`conclusion` values before checking for `'success'`:
|
|
48
|
+
|
|
49
|
+
if: >-
|
|
50
|
+
github.event.workflow_run.conclusion != '' &&
|
|
51
|
+
github.event.workflow_run.conclusion == 'success'
|
|
52
|
+
|
|
53
|
+
2. **Re-query via REST API** — Instead of trusting `github.event.workflow_run.conclusion`,
|
|
54
|
+
use the Actions REST API to fetch the current workflow run conclusion at step runtime:
|
|
55
|
+
|
|
56
|
+
gh api /repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }} \
|
|
57
|
+
--jq '.conclusion'
|
|
58
|
+
|
|
59
|
+
This always returns the up-to-date value, bypassing the event payload race condition.
|
|
60
|
+
|
|
61
|
+
Note: adding a `sleep` between trigger and conclusion check is NOT a reliable fix, as
|
|
62
|
+
the delay between trigger and backend consistency is variable and can exceed seconds.
|
|
63
|
+
fix_code:
|
|
64
|
+
- language: yaml
|
|
65
|
+
label: 'Problem: conclusion check fails on premature null trigger'
|
|
66
|
+
code: |
|
|
67
|
+
on:
|
|
68
|
+
workflow_run:
|
|
69
|
+
workflows: ['CI Build']
|
|
70
|
+
types: [completed]
|
|
71
|
+
|
|
72
|
+
jobs:
|
|
73
|
+
deploy:
|
|
74
|
+
# This randomly evaluates to false when conclusion is null
|
|
75
|
+
if: github.event.workflow_run.conclusion == 'success'
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
steps:
|
|
78
|
+
- run: echo "Deploying..."
|
|
79
|
+
- language: yaml
|
|
80
|
+
label: 'Fix: guard against null conclusion before equality check'
|
|
81
|
+
code: |
|
|
82
|
+
on:
|
|
83
|
+
workflow_run:
|
|
84
|
+
workflows: ['CI Build']
|
|
85
|
+
types: [completed]
|
|
86
|
+
|
|
87
|
+
jobs:
|
|
88
|
+
deploy:
|
|
89
|
+
# Explicitly reject null/empty conclusion to avoid premature-trigger skips
|
|
90
|
+
if: >-
|
|
91
|
+
github.event.workflow_run.conclusion != '' &&
|
|
92
|
+
github.event.workflow_run.conclusion == 'success'
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
steps:
|
|
95
|
+
- run: echo "Deploying..."
|
|
96
|
+
- language: yaml
|
|
97
|
+
label: 'Fix: re-query conclusion via REST API for reliable value'
|
|
98
|
+
code: |
|
|
99
|
+
on:
|
|
100
|
+
workflow_run:
|
|
101
|
+
workflows: ['CI Build']
|
|
102
|
+
types: [completed]
|
|
103
|
+
|
|
104
|
+
jobs:
|
|
105
|
+
deploy:
|
|
106
|
+
runs-on: ubuntu-latest
|
|
107
|
+
steps:
|
|
108
|
+
- name: Check conclusion via API
|
|
109
|
+
id: check
|
|
110
|
+
env:
|
|
111
|
+
GH_TOKEN: ${{ github.token }}
|
|
112
|
+
run: |
|
|
113
|
+
conclusion=$(gh api \
|
|
114
|
+
/repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }} \
|
|
115
|
+
--jq '.conclusion')
|
|
116
|
+
echo "conclusion=$conclusion" >> "$GITHUB_OUTPUT"
|
|
117
|
+
|
|
118
|
+
- name: Deploy
|
|
119
|
+
if: steps.check.outputs.conclusion == 'success'
|
|
120
|
+
run: echo "Deploying..."
|
|
121
|
+
prevention:
|
|
122
|
+
- "Never rely solely on `github.event.workflow_run.conclusion == 'success'` without a null guard."
|
|
123
|
+
- "Add `github.event.workflow_run.conclusion != ''` as an explicit first condition to reject premature triggers."
|
|
124
|
+
- "If downstream jobs must be fully reliable, re-query conclusion via the REST API rather than reading from the event payload."
|
|
125
|
+
- "Monitor the Actions tab for unexpectedly skipped `workflow_run` triggered runs — they indicate premature triggers."
|
|
126
|
+
docs:
|
|
127
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run'
|
|
128
|
+
label: 'GitHub Docs: workflow_run event'
|
|
129
|
+
- url: 'https://github.com/actions/runner/issues/4035'
|
|
130
|
+
label: 'actions/runner#4035: workflow_run triggers before referenced workflow completes'
|
|
131
|
+
- url: 'https://stackoverflow.com/questions/73107307/any-workaround-for-github-actions-workflow-run-conclusion-randomly-failing'
|
|
132
|
+
label: 'Stack Overflow: workflow_run.conclusion randomly failing'
|
|
133
|
+
- url: 'https://github.com/orgs/community/discussions/58929'
|
|
134
|
+
label: 'GitHub Community #58929: github.event.workflow_run.conclusion is always null'
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
id: yaml-syntax-065
|
|
2
|
+
title: 'env context unavailable in defaults.run.working-directory — "Unrecognized named-value: env"'
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- env
|
|
7
|
+
- context
|
|
8
|
+
- defaults
|
|
9
|
+
- working-directory
|
|
10
|
+
- expression
|
|
11
|
+
- context-availability
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Unrecognized named-value: ''env''.*defaults|defaults.*working.directory.*env'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'The workflow is not valid.*defaults\.run\.working-directory.*env\.'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'Unrecognized named-value: ''env''.*position.*working.directory'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- "The workflow is not valid. .github/workflows/<workflow>.yml (Line: X, Col: Y): Unrecognized named-value: 'env'. Located at position 1 within expression: env.WORKING_DIR"
|
|
21
|
+
- "Unrecognized named-value: 'env'"
|
|
22
|
+
root_cause: |
|
|
23
|
+
The `env` context is only available during step execution — not at job evaluation time.
|
|
24
|
+
`defaults.run.working-directory` is a job-level field evaluated before any steps run,
|
|
25
|
+
so GitHub Actions rejects references to `env.*` inside it with "Unrecognized named-value: 'env'".
|
|
26
|
+
|
|
27
|
+
This is the same fundamental limitation that affects other job-level fields (if:,
|
|
28
|
+
runs-on, timeout-minutes, continue-on-error) but is less documented for defaults.run.
|
|
29
|
+
|
|
30
|
+
Common patterns that fail:
|
|
31
|
+
- Setting a shared working directory from a workflow-level env variable:
|
|
32
|
+
env:
|
|
33
|
+
APP_DIR: './packages/app'
|
|
34
|
+
jobs:
|
|
35
|
+
build:
|
|
36
|
+
defaults:
|
|
37
|
+
run:
|
|
38
|
+
working-directory: ${{ env.APP_DIR }} # FAILS — env not available here
|
|
39
|
+
|
|
40
|
+
- Reading an env var set in another job:
|
|
41
|
+
jobs:
|
|
42
|
+
setup:
|
|
43
|
+
...
|
|
44
|
+
build:
|
|
45
|
+
defaults:
|
|
46
|
+
run:
|
|
47
|
+
working-directory: ${{ env.BUILD_DIR }} # FAILS — env from another job not visible
|
|
48
|
+
|
|
49
|
+
The limitation applies to all expressions in defaults.run.working-directory and
|
|
50
|
+
defaults.run.shell at both the workflow level and the job level.
|
|
51
|
+
fix: |
|
|
52
|
+
Replace env context references in defaults.run.working-directory with contexts that are
|
|
53
|
+
available at job evaluation time:
|
|
54
|
+
|
|
55
|
+
1. Use vars.* (repository or environment variables configured in Settings) for static paths
|
|
56
|
+
2. Use inputs.* if inside a reusable workflow called with workflow_call
|
|
57
|
+
3. Set working-directory on each individual step instead of at defaults level
|
|
58
|
+
4. Use an outputs chain from a prior job if the path is computed dynamically
|
|
59
|
+
fix_code:
|
|
60
|
+
- language: yaml
|
|
61
|
+
label: 'WRONG — env context in defaults.run.working-directory (fails)'
|
|
62
|
+
code: |
|
|
63
|
+
env:
|
|
64
|
+
APP_DIR: './packages/app'
|
|
65
|
+
|
|
66
|
+
jobs:
|
|
67
|
+
build:
|
|
68
|
+
defaults:
|
|
69
|
+
run:
|
|
70
|
+
working-directory: ${{ env.APP_DIR }} # Error: Unrecognized named-value 'env'
|
|
71
|
+
- language: yaml
|
|
72
|
+
label: 'FIX option 1 — use vars context (repository variable)'
|
|
73
|
+
code: |
|
|
74
|
+
# Configure APP_DIR in Settings > Secrets and variables > Variables
|
|
75
|
+
jobs:
|
|
76
|
+
build:
|
|
77
|
+
defaults:
|
|
78
|
+
run:
|
|
79
|
+
working-directory: ${{ vars.APP_DIR }} # repository variable — available at job level
|
|
80
|
+
- language: yaml
|
|
81
|
+
label: 'FIX option 2 — set working-directory on each step directly'
|
|
82
|
+
code: |
|
|
83
|
+
jobs:
|
|
84
|
+
build:
|
|
85
|
+
env:
|
|
86
|
+
APP_DIR: './packages/app'
|
|
87
|
+
steps:
|
|
88
|
+
- name: Build
|
|
89
|
+
run: npm run build
|
|
90
|
+
working-directory: ${{ env.APP_DIR }} # env IS available at step level
|
|
91
|
+
- name: Test
|
|
92
|
+
run: npm test
|
|
93
|
+
working-directory: ${{ env.APP_DIR }}
|
|
94
|
+
- language: yaml
|
|
95
|
+
label: 'FIX option 3 — hardcode the path in defaults.run'
|
|
96
|
+
code: |
|
|
97
|
+
jobs:
|
|
98
|
+
build:
|
|
99
|
+
defaults:
|
|
100
|
+
run:
|
|
101
|
+
working-directory: ./packages/app # literal path, no context expression needed
|
|
102
|
+
prevention:
|
|
103
|
+
- 'Only use vars.*, github.*, inputs.*, or needs.*.outputs.* in job-level expressions — env.* is never available at job level'
|
|
104
|
+
- 'For shared working directories, prefer literal paths in defaults.run.working-directory over expressions'
|
|
105
|
+
- 'If the path must be dynamic, use step-level working-directory instead of defaults.run'
|
|
106
|
+
- 'Use actionlint to validate workflows locally — it catches env context misuse in job-level fields'
|
|
107
|
+
docs:
|
|
108
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/setting-default-values-for-jobs'
|
|
109
|
+
label: 'GitHub Actions — Setting default values for jobs (defaults.run)'
|
|
110
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#context-availability'
|
|
111
|
+
label: 'GitHub Actions context availability by workflow key'
|
|
112
|
+
- url: 'https://github.com/rhysd/actionlint'
|
|
113
|
+
label: 'actionlint — static checker that validates context availability'
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
id: yaml-syntax-064
|
|
2
|
+
title: 'strategy.matrix.include/exclude entry value specified as array causes "A sequence was not expected"'
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- matrix
|
|
7
|
+
- include
|
|
8
|
+
- exclude
|
|
9
|
+
- array-value
|
|
10
|
+
- sequence
|
|
11
|
+
- validation-error
|
|
12
|
+
- strategy
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'A sequence was not expected'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'matrix\.(include|exclude).*\[.*\]'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
- regex: 'strategy.*matrix.*include.*sequence'
|
|
19
|
+
flags: 'i'
|
|
20
|
+
error_messages:
|
|
21
|
+
- "Error: The template is not valid. .github/workflows/workflow.yml (Line: 12, Col: 18): A sequence was not expected"
|
|
22
|
+
- "A sequence was not expected"
|
|
23
|
+
root_cause: |
|
|
24
|
+
In a `strategy.matrix`, the top-level matrix dimensions accept arrays of scalars:
|
|
25
|
+
|
|
26
|
+
php: ['8.2', '8.3', '8.4'] # valid — array of scalars
|
|
27
|
+
|
|
28
|
+
However, inside `include:` and `exclude:` entries, ALL values must be scalars
|
|
29
|
+
(string, number, boolean). Arrays are not allowed:
|
|
30
|
+
|
|
31
|
+
include:
|
|
32
|
+
- os: ubuntu-latest
|
|
33
|
+
php: ['8.2'] # INVALID — array not allowed in include/exclude entries
|
|
34
|
+
|
|
35
|
+
This is a common mistake when a developer copies the variable name from the main
|
|
36
|
+
matrix definition (where it IS an array) and pastes it into an `include:` or
|
|
37
|
+
`exclude:` entry without unwrapping it.
|
|
38
|
+
|
|
39
|
+
GitHub Actions' expression evaluator validates the template before execution and
|
|
40
|
+
throws an immediate hard failure:
|
|
41
|
+
|
|
42
|
+
Error: The template is not valid. .github/workflows/X.yml (Line: N, Col: M):
|
|
43
|
+
A sequence was not expected
|
|
44
|
+
|
|
45
|
+
The error points to the line where the array value starts (the `[` character),
|
|
46
|
+
which can be confusing because arrays are valid YAML and valid in other matrix
|
|
47
|
+
contexts. The fix is simply to remove the brackets and use a scalar value.
|
|
48
|
+
|
|
49
|
+
Tracked in rhysd/actionlint#630 (actionlint static analysis issue requesting
|
|
50
|
+
detection of this pattern).
|
|
51
|
+
fix: |
|
|
52
|
+
Replace any array-valued entries inside `include:` or `exclude:` with scalar values.
|
|
53
|
+
Each `include:`/`exclude:` entry is a single-combination filter — it selects or adds
|
|
54
|
+
one specific combination, so the value must be a scalar that matches exactly one item.
|
|
55
|
+
|
|
56
|
+
If you need to include multiple values for a key, write separate `include:` entries,
|
|
57
|
+
one per value.
|
|
58
|
+
fix_code:
|
|
59
|
+
- language: yaml
|
|
60
|
+
label: 'Problem: array value inside include entry — triggers "A sequence was not expected"'
|
|
61
|
+
code: |
|
|
62
|
+
strategy:
|
|
63
|
+
matrix:
|
|
64
|
+
os: [ubuntu-latest, macos-latest]
|
|
65
|
+
php: ['8.3', '8.4', '8.5']
|
|
66
|
+
|
|
67
|
+
include:
|
|
68
|
+
# ERROR: php value is an array — not allowed inside include entries
|
|
69
|
+
- os: ubuntu-latest
|
|
70
|
+
php: ['8.2'] # ← "A sequence was not expected" on this line
|
|
71
|
+
|
|
72
|
+
exclude:
|
|
73
|
+
# ERROR: os value is an array — not allowed inside exclude entries
|
|
74
|
+
- os: [macos-latest]
|
|
75
|
+
php: '8.3' # ← "A sequence was not expected"
|
|
76
|
+
- language: yaml
|
|
77
|
+
label: 'Fix: use scalar values inside include/exclude entries'
|
|
78
|
+
code: |
|
|
79
|
+
strategy:
|
|
80
|
+
matrix:
|
|
81
|
+
os: [ubuntu-latest, macos-latest]
|
|
82
|
+
php: ['8.3', '8.4', '8.5']
|
|
83
|
+
|
|
84
|
+
include:
|
|
85
|
+
# CORRECT: scalar value — adds ubuntu-latest × 8.2 as an extra job
|
|
86
|
+
- os: ubuntu-latest
|
|
87
|
+
php: '8.2' # ← scalar, not array
|
|
88
|
+
|
|
89
|
+
exclude:
|
|
90
|
+
# CORRECT: scalar value — excludes macos-latest × 8.3 combination
|
|
91
|
+
- os: macos-latest
|
|
92
|
+
php: '8.3' # ← scalar, not array
|
|
93
|
+
- language: yaml
|
|
94
|
+
label: 'Multiple include entries for multiple specific combinations'
|
|
95
|
+
code: |
|
|
96
|
+
strategy:
|
|
97
|
+
matrix:
|
|
98
|
+
os: [ubuntu-latest, macos-latest]
|
|
99
|
+
node: [18, 20]
|
|
100
|
+
|
|
101
|
+
include:
|
|
102
|
+
# To include multiple specific combinations, write separate entries:
|
|
103
|
+
- os: ubuntu-latest
|
|
104
|
+
node: 22 # one entry per extra combination
|
|
105
|
+
- os: ubuntu-latest
|
|
106
|
+
node: 23 # another entry for a different combination
|
|
107
|
+
# NOT: node: [22, 23] ← this would fail with "A sequence was not expected"
|
|
108
|
+
prevention:
|
|
109
|
+
- "Values inside `include:` and `exclude:` entries must be scalars — never use array syntax `[...]` there."
|
|
110
|
+
- "When copying matrix variable names from the top-level dimensions to include/exclude entries, remove the brackets."
|
|
111
|
+
- "Use actionlint (github.com/rhysd/actionlint) to statically validate your workflow files before pushing."
|
|
112
|
+
- "If you need multiple combinations, write multiple separate `include:` entries instead of one entry with an array value."
|
|
113
|
+
docs:
|
|
114
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow#expanding-or-adding-matrix-configurations'
|
|
115
|
+
label: 'GitHub Docs: Expanding or adding matrix configurations'
|
|
116
|
+
- url: 'https://github.com/rhysd/actionlint/issues/630'
|
|
117
|
+
label: 'rhysd/actionlint#630: Detect array values in matrix include/exclude entries'
|
|
118
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrixinclude'
|
|
119
|
+
label: 'GitHub Docs: jobs.<job_id>.strategy.matrix.include syntax reference'
|
package/package.json
CHANGED