@htekdev/actions-debugger 1.0.50 → 1.0.52
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-036.yml +155 -0
- package/errors/permissions-auth/permissions-auth-038.yml +148 -0
- package/errors/runner-environment/runner-environment-106.yml +110 -0
- package/errors/runner-environment/runner-environment-107.yml +105 -0
- package/errors/runner-environment/runner-environment-108.yml +109 -0
- package/errors/runner-environment/runner-environment-109.yml +121 -0
- package/errors/runner-environment/runner-environment-110.yml +113 -0
- package/errors/silent-failures/silent-failures-053.yml +111 -0
- package/package.json +1 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
id: caching-artifacts-036
|
|
2
|
+
title: 'actions/cache post step skips save when job is cancelled — cache never populated after cancellation'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- actions-cache
|
|
7
|
+
- job-cancellation
|
|
8
|
+
- post-step
|
|
9
|
+
- cache-save
|
|
10
|
+
- cancel-in-progress
|
|
11
|
+
- concurrency
|
|
12
|
+
- always
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'Post\s+Run\s+actions/cache.*skipped|Cache\s+save\s+skipped'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'uses:\s*actions/cache@'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
error_messages:
|
|
19
|
+
- 'Post Run actions/cache@v4 skipped'
|
|
20
|
+
- 'Cache save skipped'
|
|
21
|
+
- '##[warning]Cache save skipped.'
|
|
22
|
+
- 'Warning: Cache save failed.'
|
|
23
|
+
root_cause: |
|
|
24
|
+
The actions/cache action saves the cache in a lifecycle "post" step that runs
|
|
25
|
+
automatically after the main job steps complete. When a job is cancelled — via
|
|
26
|
+
the Actions UI, concurrency cancel-in-progress, or exceeding job timeout-minutes
|
|
27
|
+
— post steps do NOT execute.
|
|
28
|
+
|
|
29
|
+
This creates a cache starvation loop:
|
|
30
|
+
1. Job starts with cache miss — slow dependency installation begins (minutes)
|
|
31
|
+
2. A new push triggers another workflow run while the first is still installing
|
|
32
|
+
3. concurrency: cancel-in-progress terminates the first run mid-install
|
|
33
|
+
4. The first run never reaches its post step — cache is not saved
|
|
34
|
+
5. The second run also sees a cache miss — the same slow install repeats
|
|
35
|
+
6. Repeated cancellations mean the cache is never populated
|
|
36
|
+
|
|
37
|
+
This is invisible in workflow logs: cancelled jobs simply show as cancelled with
|
|
38
|
+
no specific warning about the cache post step being skipped. Teams diagnose it
|
|
39
|
+
as "caching isn't working" without realizing cancellation is the root cause.
|
|
40
|
+
|
|
41
|
+
Affected workflows:
|
|
42
|
+
- Any workflow with concurrency: cancel-in-progress that also uses actions/cache
|
|
43
|
+
- Workflows with aggressive job timeout-minutes that expire during installs
|
|
44
|
+
- Flaky workflows that are frequently manually cancelled and re-run
|
|
45
|
+
- Matrix workflows where one failing matrix branch cancels siblings
|
|
46
|
+
|
|
47
|
+
When a job fails (but is not cancelled), post steps DO run normally — the issue
|
|
48
|
+
is specific to cancellations and hard timeouts.
|
|
49
|
+
fix: |
|
|
50
|
+
Use the split actions/cache/restore + actions/cache/save pattern with
|
|
51
|
+
if: always() on the save step. This gives explicit control over cache saving
|
|
52
|
+
independent of the job lifecycle hooks:
|
|
53
|
+
|
|
54
|
+
- uses: actions/cache/restore@v4
|
|
55
|
+
id: cache-restore
|
|
56
|
+
with:
|
|
57
|
+
key: ...
|
|
58
|
+
path: ...
|
|
59
|
+
|
|
60
|
+
# ... install and build steps ...
|
|
61
|
+
|
|
62
|
+
- uses: actions/cache/save@v4
|
|
63
|
+
if: always()
|
|
64
|
+
with:
|
|
65
|
+
key: ${{ steps.cache-restore.outputs.cache-primary-key }}
|
|
66
|
+
path: ...
|
|
67
|
+
|
|
68
|
+
The split restore/save pattern (available since actions/cache v3.3.0 in 2023)
|
|
69
|
+
runs the save as an explicit step with if: always(), which executes during the
|
|
70
|
+
graceful shutdown window even when the job is being cancelled.
|
|
71
|
+
|
|
72
|
+
Important caveat: if the runner process is killed with SIGKILL (hard preemption,
|
|
73
|
+
host failure), no steps including if: always() will run — this is a
|
|
74
|
+
platform-level constraint outside GitHub Actions' control.
|
|
75
|
+
fix_code:
|
|
76
|
+
- language: yaml
|
|
77
|
+
label: 'Split restore/save pattern with unconditional save to survive cancellation'
|
|
78
|
+
code: |
|
|
79
|
+
jobs:
|
|
80
|
+
build:
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
# Even with cancel-in-progress, the explicit save step will run
|
|
83
|
+
concurrency:
|
|
84
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
85
|
+
cancel-in-progress: true
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/checkout@v4
|
|
88
|
+
|
|
89
|
+
- name: Restore cached dependencies
|
|
90
|
+
uses: actions/cache/restore@v4
|
|
91
|
+
id: cache-restore
|
|
92
|
+
with:
|
|
93
|
+
path: ~/.npm
|
|
94
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
95
|
+
restore-keys: |
|
|
96
|
+
${{ runner.os }}-npm-
|
|
97
|
+
|
|
98
|
+
- name: Install dependencies
|
|
99
|
+
if: steps.cache-restore.outputs.cache-hit != 'true'
|
|
100
|
+
run: npm ci
|
|
101
|
+
|
|
102
|
+
- name: Run tests
|
|
103
|
+
run: npm test
|
|
104
|
+
|
|
105
|
+
- name: Save cache
|
|
106
|
+
uses: actions/cache/save@v4
|
|
107
|
+
if: always() # Runs during graceful cancellation shutdown window
|
|
108
|
+
with:
|
|
109
|
+
path: ~/.npm
|
|
110
|
+
key: ${{ steps.cache-restore.outputs.cache-primary-key }}
|
|
111
|
+
|
|
112
|
+
- language: yaml
|
|
113
|
+
label: 'Python pip cache with split save pattern'
|
|
114
|
+
code: |
|
|
115
|
+
jobs:
|
|
116
|
+
test:
|
|
117
|
+
runs-on: ubuntu-latest
|
|
118
|
+
steps:
|
|
119
|
+
- uses: actions/checkout@v4
|
|
120
|
+
|
|
121
|
+
- name: Restore pip cache
|
|
122
|
+
uses: actions/cache/restore@v4
|
|
123
|
+
id: pip-cache
|
|
124
|
+
with:
|
|
125
|
+
path: ~/.cache/pip
|
|
126
|
+
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
|
|
127
|
+
restore-keys: |
|
|
128
|
+
${{ runner.os }}-pip-
|
|
129
|
+
|
|
130
|
+
- name: Install Python dependencies
|
|
131
|
+
run: pip install -r requirements.txt
|
|
132
|
+
|
|
133
|
+
- name: Run pytest
|
|
134
|
+
run: pytest
|
|
135
|
+
|
|
136
|
+
- name: Save pip cache
|
|
137
|
+
uses: actions/cache/save@v4
|
|
138
|
+
if: always()
|
|
139
|
+
with:
|
|
140
|
+
path: ~/.cache/pip
|
|
141
|
+
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
|
|
142
|
+
prevention:
|
|
143
|
+
- 'Prefer the split actions/cache/restore + actions/cache/save pattern over the combined actions/cache action for any workflow using concurrency: cancel-in-progress'
|
|
144
|
+
- 'Always add if: always() to explicit cache save steps so they execute regardless of upstream step failure or cancellation signal'
|
|
145
|
+
- 'Monitor workflow logs for "Post Run actions/cache skipped" messages — this is a reliable indicator that cache is never being populated after cancellations'
|
|
146
|
+
- 'Use actions/cache v3.3.0 or later — the split restore/save subactions were introduced in that release'
|
|
147
|
+
docs:
|
|
148
|
+
- url: 'https://github.com/actions/cache/blob/main/save/README.md'
|
|
149
|
+
label: 'actions/cache: Explicit save action documentation'
|
|
150
|
+
- url: 'https://github.com/actions/cache#save-action'
|
|
151
|
+
label: 'actions/cache: Split restore and save usage pattern'
|
|
152
|
+
- url: 'https://github.com/orgs/community/discussions/47469'
|
|
153
|
+
label: 'GitHub Community #47469: Cache not saved when workflow is cancelled'
|
|
154
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-syntax-for-github-actions#jobsjob_idstepsif'
|
|
155
|
+
label: 'GitHub Docs: Using conditions to control job execution'
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
id: permissions-auth-038
|
|
2
|
+
title: 'secrets: inherit does not propagate environment-scoped secrets to reusable workflows'
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- secrets-inherit
|
|
7
|
+
- reusable-workflows
|
|
8
|
+
- environment-secrets
|
|
9
|
+
- workflow-call
|
|
10
|
+
- empty-secret
|
|
11
|
+
- deployment-environment
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'secrets:\s*inherit'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'uses:\s*[./\w@-]+\.ya?ml\b'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- 'Error: Input required and not supplied'
|
|
19
|
+
- 'Secret value is empty in reusable workflow'
|
|
20
|
+
- '##[error]The secret variable name is invalid'
|
|
21
|
+
root_cause: |
|
|
22
|
+
When a caller workflow uses secrets: inherit to call a reusable workflow
|
|
23
|
+
(on.workflow_call), GitHub passes repository-level and organization-level
|
|
24
|
+
secrets to the called workflow. However, environment-scoped secrets are NOT
|
|
25
|
+
inherited — they arrive as empty strings with no error or warning.
|
|
26
|
+
|
|
27
|
+
Environment secrets are tied to a named deployment environment (e.g.,
|
|
28
|
+
"production", "staging") and are only accessible to jobs that declare
|
|
29
|
+
environment: <name> in their configuration. The secrets: inherit mechanism
|
|
30
|
+
operates at the repository/org secret scope level. It cannot bridge environment
|
|
31
|
+
context to a called workflow's jobs because those jobs run in a separate
|
|
32
|
+
workflow execution context and do not inherit the caller job's environment
|
|
33
|
+
declaration.
|
|
34
|
+
|
|
35
|
+
Failure scenario:
|
|
36
|
+
1. Caller job declares environment: production (activates env secrets in the caller)
|
|
37
|
+
2. Caller job uses secrets: inherit to call ./reusable.yml
|
|
38
|
+
3. Reusable workflow job reads secrets.PROD_DB_PASSWORD (an environment secret)
|
|
39
|
+
4. Reusable workflow job sees PROD_DB_PASSWORD as empty string
|
|
40
|
+
5. No error is thrown — the secret silently resolves to ""
|
|
41
|
+
|
|
42
|
+
This is documented as a GitHub platform limitation in the reusable workflows
|
|
43
|
+
documentation. GitHub Community discussion #26671 has many reports of this
|
|
44
|
+
surprising behavior.
|
|
45
|
+
|
|
46
|
+
Distinction from permissions-auth-037 (environment-secrets-job-scope):
|
|
47
|
+
- permissions-auth-037 covers jobs in the SAME workflow that lack environment:
|
|
48
|
+
declarations failing to access environment secrets.
|
|
49
|
+
- This entry covers the cross-workflow boundary where secrets: inherit does not
|
|
50
|
+
transfer environment secrets from caller to called workflow at all.
|
|
51
|
+
fix: |
|
|
52
|
+
Explicitly declare and map environment secrets at the workflow_call boundary.
|
|
53
|
+
In the reusable workflow, declare each secret in the on.workflow_call.secrets
|
|
54
|
+
block. At the call site, explicitly pass each environment secret using the
|
|
55
|
+
secrets: mapping block (not secrets: inherit):
|
|
56
|
+
|
|
57
|
+
In the reusable workflow (workflow_call trigger):
|
|
58
|
+
on:
|
|
59
|
+
workflow_call:
|
|
60
|
+
secrets:
|
|
61
|
+
db_password:
|
|
62
|
+
required: true
|
|
63
|
+
api_key:
|
|
64
|
+
required: false
|
|
65
|
+
|
|
66
|
+
In the calling workflow:
|
|
67
|
+
jobs:
|
|
68
|
+
deploy:
|
|
69
|
+
environment: production
|
|
70
|
+
uses: ./.github/workflows/deploy.yml
|
|
71
|
+
secrets:
|
|
72
|
+
db_password: ${{ secrets.PROD_DB_PASSWORD }}
|
|
73
|
+
api_key: ${{ secrets.PROD_API_KEY }}
|
|
74
|
+
|
|
75
|
+
Alternatively, if the reusable workflow is in the same repository and the
|
|
76
|
+
environment name is fixed, you can declare environment: production on the job
|
|
77
|
+
inside the reusable workflow directly. This makes environment secrets accessible
|
|
78
|
+
in the called workflow at the cost of coupling it to a specific environment name.
|
|
79
|
+
|
|
80
|
+
Note: secrets: inherit and explicit secrets: mapping cannot be combined on the
|
|
81
|
+
same uses: step. Choose one approach: either inherit all repo/org secrets, or
|
|
82
|
+
map explicitly (which also allows passing environment secrets).
|
|
83
|
+
fix_code:
|
|
84
|
+
- language: yaml
|
|
85
|
+
label: 'Reusable workflow — declare required secrets in workflow_call trigger'
|
|
86
|
+
code: |
|
|
87
|
+
# .github/workflows/deploy-reusable.yml
|
|
88
|
+
on:
|
|
89
|
+
workflow_call:
|
|
90
|
+
secrets:
|
|
91
|
+
db_password:
|
|
92
|
+
required: true
|
|
93
|
+
api_key:
|
|
94
|
+
required: true
|
|
95
|
+
|
|
96
|
+
jobs:
|
|
97
|
+
deploy:
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
steps:
|
|
100
|
+
- name: Deploy application
|
|
101
|
+
env:
|
|
102
|
+
DB_PASSWORD: ${{ secrets.db_password }}
|
|
103
|
+
API_KEY: ${{ secrets.api_key }}
|
|
104
|
+
run: ./scripts/deploy.sh
|
|
105
|
+
|
|
106
|
+
- language: yaml
|
|
107
|
+
label: 'Caller workflow — explicitly map environment secrets (not secrets: inherit)'
|
|
108
|
+
code: |
|
|
109
|
+
# .github/workflows/deploy-caller.yml
|
|
110
|
+
jobs:
|
|
111
|
+
call-deploy:
|
|
112
|
+
environment: production # Activates environment secrets in this calling job
|
|
113
|
+
uses: ./.github/workflows/deploy-reusable.yml
|
|
114
|
+
# WRONG: secrets: inherit -- passes repo/org secrets but NOT environment secrets
|
|
115
|
+
secrets:
|
|
116
|
+
db_password: ${{ secrets.PROD_DB_PASSWORD }} # Environment secret — must be explicit
|
|
117
|
+
api_key: ${{ secrets.PROD_API_KEY }} # Environment secret — must be explicit
|
|
118
|
+
|
|
119
|
+
- language: yaml
|
|
120
|
+
label: 'Alternative — declare environment inside the reusable workflow job'
|
|
121
|
+
code: |
|
|
122
|
+
# .github/workflows/deploy-reusable.yml (alternative approach)
|
|
123
|
+
on:
|
|
124
|
+
workflow_call: {}
|
|
125
|
+
|
|
126
|
+
jobs:
|
|
127
|
+
deploy:
|
|
128
|
+
runs-on: ubuntu-latest
|
|
129
|
+
environment: production # Directly activates environment secrets here
|
|
130
|
+
steps: # Couples reusable workflow to environment name
|
|
131
|
+
- name: Deploy application
|
|
132
|
+
env:
|
|
133
|
+
DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
|
|
134
|
+
run: ./scripts/deploy.sh
|
|
135
|
+
prevention:
|
|
136
|
+
- 'Never rely on secrets: inherit to pass environment-scoped secrets — always map them explicitly in the secrets: block of the workflow_call invocation'
|
|
137
|
+
- 'In reusable workflows, declare all required secrets in on.workflow_call.secrets with required: true so missing secrets fail loudly rather than silently resolving to empty strings'
|
|
138
|
+
- 'Document which secrets in your reusable workflow are environment-scoped vs repository-scoped so callers know the correct passing strategy'
|
|
139
|
+
- 'Test reusable workflows end-to-end from a caller workflow before deploying to production — secrets: inherit working in testing does not guarantee environment secrets work correctly'
|
|
140
|
+
docs:
|
|
141
|
+
- url: 'https://docs.github.com/en/actions/sharing-automations/reusing-workflows#passing-secrets-to-called-workflows'
|
|
142
|
+
label: 'GitHub Docs: Passing secrets to called workflows'
|
|
143
|
+
- url: 'https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-an-environment'
|
|
144
|
+
label: 'GitHub Docs: Creating secrets for an environment'
|
|
145
|
+
- url: 'https://github.com/orgs/community/discussions/26671'
|
|
146
|
+
label: 'GitHub Community #26671: secrets: inherit not passing environment secrets'
|
|
147
|
+
- url: 'https://docs.github.com/en/actions/sharing-automations/reusing-workflows#limitations'
|
|
148
|
+
label: 'GitHub Docs: Reusable workflows — limitations'
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
id: runner-environment-106
|
|
2
|
+
title: 'Windows Server 2019 (windows-2019) runner retired April 1, 2025 — jobs fail or queue indefinitely'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- windows-2019
|
|
7
|
+
- runner-retirement
|
|
8
|
+
- windows-server-2019
|
|
9
|
+
- deprecated-runner
|
|
10
|
+
- github-hosted
|
|
11
|
+
- runs-on
|
|
12
|
+
- visual-studio-2019
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'runs-on:\s*windows-2019'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'No runner matching the specified labels was found.*windows-2019|Requested labels:\s*windows-2019'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
error_messages:
|
|
19
|
+
- 'No runner matching the specified labels was found: windows-2019'
|
|
20
|
+
- '##[error]No runner matching the specified labels was found'
|
|
21
|
+
- 'Runner not found matching labels: [windows-2019]'
|
|
22
|
+
root_cause: |
|
|
23
|
+
GitHub retired the Windows Server 2019 (windows-2019) GitHub-hosted runner on
|
|
24
|
+
April 1, 2025. After this date, workflows specifying runs-on: windows-2019 can
|
|
25
|
+
no longer be scheduled on a GitHub-hosted runner matching that label.
|
|
26
|
+
|
|
27
|
+
GitHub announced the deprecation in September 2024 (90+ days notice) with
|
|
28
|
+
brownout periods beginning before the hard cutoff. windows-latest had already
|
|
29
|
+
transitioned to point to windows-2022 (Windows Server 2022 with Visual Studio
|
|
30
|
+
2022) on October 28, 2024, giving teams an early signal.
|
|
31
|
+
|
|
32
|
+
Workflows most affected:
|
|
33
|
+
- Files explicitly specifying runs-on: windows-2019 (not windows-latest)
|
|
34
|
+
- Builds relying on Visual Studio 2019 toolset version v142 MSBuild tools
|
|
35
|
+
- .csproj files with hardcoded <PlatformToolset>v142</PlatformToolset>
|
|
36
|
+
- Workflows using .NET Framework or SDK behaviors specific to the VS2019 era
|
|
37
|
+
- Actions pinned to a major version that internally specified windows-2019
|
|
38
|
+
- Forks and template repositories written before windows-2022 was the default
|
|
39
|
+
|
|
40
|
+
A secondary migration concern: workflows that used windows-latest and relied on
|
|
41
|
+
VS2019 behavior were silently broken when windows-latest moved to windows-2022
|
|
42
|
+
in October 2024. Pinning to windows-2019 was a common workaround — the
|
|
43
|
+
retirement forced resolution of both the workaround and the underlying toolset
|
|
44
|
+
incompatibility simultaneously.
|
|
45
|
+
fix: |
|
|
46
|
+
Replace runs-on: windows-2019 with a supported Windows runner label:
|
|
47
|
+
|
|
48
|
+
- windows-2022 — Windows Server 2022, Visual Studio 2022 (toolset v143)
|
|
49
|
+
- windows-2025 — Windows Server 2025, Visual Studio 2022 (available 2025)
|
|
50
|
+
- windows-latest — currently maps to windows-2022
|
|
51
|
+
|
|
52
|
+
If your workflow uses MSBuild and specifies PlatformToolset=v142 (VS2019
|
|
53
|
+
toolset), you have two options:
|
|
54
|
+
1. Update project files to remove the explicit PlatformToolset element and
|
|
55
|
+
let MSBuild select the installed toolset automatically (preferred).
|
|
56
|
+
2. Migrate the .csproj to PlatformToolset v143 (VS2022 toolset).
|
|
57
|
+
|
|
58
|
+
Check these locations for VS2019-specific behavior:
|
|
59
|
+
- .csproj files with <PlatformToolset>v142</PlatformToolset>
|
|
60
|
+
- Hardcoded paths like C:\Program Files (x86)\Microsoft Visual Studio\2019\...
|
|
61
|
+
- vcpkg toolchain files referencing VS2019 installs
|
|
62
|
+
- Workflow env: blocks with hardcoded VSINSTALLDIR paths
|
|
63
|
+
fix_code:
|
|
64
|
+
- language: yaml
|
|
65
|
+
label: 'Replace retired windows-2019 with windows-2022'
|
|
66
|
+
code: |
|
|
67
|
+
jobs:
|
|
68
|
+
build:
|
|
69
|
+
# Before: runs-on: windows-2019 <- retired April 1, 2025
|
|
70
|
+
runs-on: windows-2022
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@v4
|
|
73
|
+
|
|
74
|
+
- name: Setup MSBuild
|
|
75
|
+
uses: microsoft/setup-msbuild@v2
|
|
76
|
+
|
|
77
|
+
- name: Build solution
|
|
78
|
+
run: msbuild solution.sln /p:Configuration=Release /p:Platform="Any CPU"
|
|
79
|
+
# If build fails on toolset version, update PlatformToolset in .csproj
|
|
80
|
+
# from v142 (VS2019) to v143 (VS2022) or remove it to auto-select
|
|
81
|
+
|
|
82
|
+
- language: yaml
|
|
83
|
+
label: 'Matrix across Windows versions to verify migration before cutover'
|
|
84
|
+
code: |
|
|
85
|
+
jobs:
|
|
86
|
+
build:
|
|
87
|
+
strategy:
|
|
88
|
+
matrix:
|
|
89
|
+
# windows-2019 removed — retired April 1, 2025
|
|
90
|
+
os: [windows-2022]
|
|
91
|
+
fail-fast: false
|
|
92
|
+
runs-on: ${{ matrix.os }}
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@v4
|
|
95
|
+
- name: Setup MSBuild
|
|
96
|
+
uses: microsoft/setup-msbuild@v2
|
|
97
|
+
- name: Build
|
|
98
|
+
run: msbuild solution.sln /p:Configuration=Release
|
|
99
|
+
prevention:
|
|
100
|
+
- 'Use windows-latest or windows-2022 — never pin to a specific older Windows runner label for long-lived workflows'
|
|
101
|
+
- 'Avoid hardcoding Visual Studio toolset versions (v142, v143) in .csproj files — let MSBuild auto-select the installed toolset'
|
|
102
|
+
- 'Subscribe to GitHub Changelog at github.blog/changelog to receive runner retirement announcements before hard cutoff dates'
|
|
103
|
+
- 'Test on windows-2022 in a feature branch before any deprecation deadline to catch VS toolset migration issues early'
|
|
104
|
+
docs:
|
|
105
|
+
- url: 'https://github.blog/changelog/2024-09-12-windows-2019-actions-runner-image-brownout-and-deprecation/'
|
|
106
|
+
label: 'GitHub Changelog: Windows 2019 runner image brownout and deprecation'
|
|
107
|
+
- url: 'https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources'
|
|
108
|
+
label: 'GitHub Docs: Supported GitHub-hosted runners and hardware resources'
|
|
109
|
+
- url: 'https://github.com/actions/runner-images'
|
|
110
|
+
label: 'actions/runner-images: GitHub-hosted runner image specifications'
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
id: runner-environment-107
|
|
2
|
+
title: 'Ubuntu 24.04 runner: unversioned python command absent — /usr/bin/env: python: No such file or directory'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- ubuntu-24
|
|
7
|
+
- python
|
|
8
|
+
- python3
|
|
9
|
+
- unversioned-alias
|
|
10
|
+
- command-not-found
|
|
11
|
+
- ubuntu-noble
|
|
12
|
+
- ubuntu-latest
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: '/usr/bin/env:\s*[''"]?python[''"]?:\s*No such file|python:\s*command not found'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'runs-on:\s*ubuntu-2[4-9]|runs-on:\s*ubuntu-latest'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
error_messages:
|
|
19
|
+
- '/usr/bin/env: python: No such file or directory'
|
|
20
|
+
- 'python: command not found'
|
|
21
|
+
- '##[error]Process completed with exit code 127.'
|
|
22
|
+
root_cause: |
|
|
23
|
+
Ubuntu 24.04 (Noble Numbat) does not install the unversioned python command by
|
|
24
|
+
default. Only python3 (Python 3.12+) is present in the PATH. This follows
|
|
25
|
+
PEP 394 guidance and a deliberate Ubuntu packaging decision: the
|
|
26
|
+
python3-is-python package (which creates a /usr/bin/python -> python3 symlink)
|
|
27
|
+
is not pre-installed on GitHub-hosted Ubuntu 24.04 runners.
|
|
28
|
+
|
|
29
|
+
As of November 2024, ubuntu-latest on GitHub-hosted runners maps to ubuntu-24.04.
|
|
30
|
+
Workflows that ran on ubuntu-latest (previously ubuntu-22.04) began failing
|
|
31
|
+
because Ubuntu 22.04 runners had side-effected python aliases through
|
|
32
|
+
actions/setup-python or the python-is-python3 shim, while the Ubuntu 24.04
|
|
33
|
+
baseline has no unversioned python at all.
|
|
34
|
+
|
|
35
|
+
Affected patterns:
|
|
36
|
+
- run: python script.py
|
|
37
|
+
- run: python -m pytest
|
|
38
|
+
- Shell scripts with #!/usr/bin/env python shebang lines
|
|
39
|
+
- Makefile targets invoking python
|
|
40
|
+
- Third-party composite actions that call python internally without setup-python
|
|
41
|
+
- pip invocations — pip is also absent (only pip3 available)
|
|
42
|
+
fix: |
|
|
43
|
+
Option 1 (recommended): Use actions/setup-python before any step that needs
|
|
44
|
+
Python. This installs a versioned Python and creates both python and python3
|
|
45
|
+
symlinks in PATH, resolving the problem portably across all runner OS versions.
|
|
46
|
+
|
|
47
|
+
Option 2: Replace all python invocations with python3 in workflow files.
|
|
48
|
+
Also replace pip with pip3 or python3 -m pip. Works if you control all scripts
|
|
49
|
+
but misses shebangs in vendor code or third-party tools.
|
|
50
|
+
|
|
51
|
+
Option 3: Install the alias manually in a setup step:
|
|
52
|
+
sudo apt-get install -y python-is-python3
|
|
53
|
+
This creates the /usr/bin/python -> python3 symlink immediately. The package is
|
|
54
|
+
already in the apt cache — install is fast with no download required.
|
|
55
|
+
fix_code:
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: 'Use actions/setup-python (recommended — portable across all runner versions)'
|
|
58
|
+
code: |
|
|
59
|
+
jobs:
|
|
60
|
+
test:
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v4
|
|
64
|
+
|
|
65
|
+
- name: Set up Python
|
|
66
|
+
uses: actions/setup-python@v5
|
|
67
|
+
with:
|
|
68
|
+
python-version: '3.12'
|
|
69
|
+
# Creates both python and python3 aliases in PATH
|
|
70
|
+
|
|
71
|
+
- name: Install dependencies
|
|
72
|
+
run: python -m pip install -r requirements.txt
|
|
73
|
+
|
|
74
|
+
- name: Run tests
|
|
75
|
+
run: python -m pytest tests/
|
|
76
|
+
|
|
77
|
+
- language: yaml
|
|
78
|
+
label: 'Install python-is-python3 alias for scripts with hardcoded python shebangs'
|
|
79
|
+
code: |
|
|
80
|
+
jobs:
|
|
81
|
+
build:
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
steps:
|
|
84
|
+
- uses: actions/checkout@v4
|
|
85
|
+
|
|
86
|
+
- name: Install python alias
|
|
87
|
+
run: sudo apt-get install -y python-is-python3
|
|
88
|
+
# Creates /usr/bin/python -> python3 symlink
|
|
89
|
+
# Fast: package already in apt cache on ubuntu-24.04
|
|
90
|
+
|
|
91
|
+
- name: Run script with python shebang
|
|
92
|
+
run: ./scripts/legacy-build.sh
|
|
93
|
+
# Script uses #!/usr/bin/env python internally
|
|
94
|
+
prevention:
|
|
95
|
+
- 'Always use actions/setup-python in workflows that invoke python — portable and creates the python alias on all runner OS versions'
|
|
96
|
+
- 'Avoid relying on system Python; python version and alias availability varies between Ubuntu 22.04 and 24.04'
|
|
97
|
+
- 'When ubuntu-latest bumps to a new Ubuntu version, search run: blocks and scripts for bare python and pip invocations'
|
|
98
|
+
- 'Pin ubuntu-24.04 explicitly during transition testing rather than ubuntu-latest to catch breakage before it hits production'
|
|
99
|
+
docs:
|
|
100
|
+
- url: 'https://github.com/actions/runner-images/issues/9654'
|
|
101
|
+
label: 'runner-images#9654: python command not found on Ubuntu 24.04'
|
|
102
|
+
- url: 'https://github.com/actions/runner-images'
|
|
103
|
+
label: 'actions/runner-images: GitHub-hosted runner image specifications'
|
|
104
|
+
- url: 'https://github.com/actions/setup-python'
|
|
105
|
+
label: 'actions/setup-python: Set up a Python environment for use in Actions'
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
id: runner-environment-108
|
|
2
|
+
title: 'Ubuntu 22.04 runner: libssl.so.1.1 missing — binaries compiled on Ubuntu 20.04 fail with cannot open shared object file'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- ubuntu-22
|
|
7
|
+
- libssl
|
|
8
|
+
- openssl3
|
|
9
|
+
- shared-library
|
|
10
|
+
- binary-compatibility
|
|
11
|
+
- ubuntu-jammy
|
|
12
|
+
- dynamic-linking
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'libssl\.so\.1\.1.*cannot open shared object file|error while loading shared libraries: libssl\.so\.1\.1'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'libcrypto\.so\.1\.1.*cannot open shared object file'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
error_messages:
|
|
19
|
+
- 'error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory'
|
|
20
|
+
- '/usr/lib/x86_64-linux-gnu/libssl.so.1.1: No such file or directory'
|
|
21
|
+
- 'error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file: No such file or directory'
|
|
22
|
+
root_cause: |
|
|
23
|
+
Ubuntu 22.04 (Jammy) ships OpenSSL 3.0, replacing OpenSSL 1.1. The shared
|
|
24
|
+
library libssl.so.1.1 is absent — the libssl1.1 package that provided it is
|
|
25
|
+
not available in the Ubuntu 22.04 (jammy) package repository at all; it was
|
|
26
|
+
a focal (Ubuntu 20.04) package only.
|
|
27
|
+
|
|
28
|
+
When ubuntu-latest transitioned from Ubuntu 20.04 to Ubuntu 22.04 in 2022,
|
|
29
|
+
CI pipelines that downloaded or shipped pre-compiled binaries started failing
|
|
30
|
+
because those binaries were dynamically linked against libssl1.1. Common
|
|
31
|
+
scenarios that trigger this error:
|
|
32
|
+
|
|
33
|
+
- Self-hosted runners migrated from Ubuntu 20.04 to 22.04 while tool cache
|
|
34
|
+
contains pre-compiled focal binaries
|
|
35
|
+
- Docker images with pre-compiled binaries using libssl1.1 run in container
|
|
36
|
+
jobs on ubuntu-22.04 hosts
|
|
37
|
+
- Ruby gems with native extensions (mysql2, pg, ruby-openssl) compiled on
|
|
38
|
+
Ubuntu 20.04 and installed from a cached bundler path
|
|
39
|
+
- Node.js native add-ons (node-gyp built) that link against libssl1.1
|
|
40
|
+
- Custom CLI tools shipped as pre-built .deb or tar.gz for Ubuntu 20.04
|
|
41
|
+
- Python C extension packages (pycurl, cryptography old versions) with
|
|
42
|
+
libssl1.1 runtime dependency
|
|
43
|
+
|
|
44
|
+
libcrypto.so.1.1 is also absent — tools linked against both libraries fail
|
|
45
|
+
with the same error on whichever library is loaded first.
|
|
46
|
+
fix: |
|
|
47
|
+
Option 1 (best long-term): Recompile the affected binary for Ubuntu 22.04 or
|
|
48
|
+
later against OpenSSL 3.0 (libssl3). Ubuntu 20.04 reached end-of-standard-support
|
|
49
|
+
in April 2025 — new binaries should target OpenSSL 3.0.
|
|
50
|
+
|
|
51
|
+
Option 2: Install a libssl1.1 compatibility .deb backported from Ubuntu 20.04
|
|
52
|
+
focal. Add an install step using the focal security archive. This is a
|
|
53
|
+
temporary workaround — the package receives no security patches on 22.04.
|
|
54
|
+
|
|
55
|
+
Option 3: Use a container job with ubuntu:20.04 base image for steps that
|
|
56
|
+
require libssl1.1, isolating the dependency from the host runner OS.
|
|
57
|
+
|
|
58
|
+
Option 4: Pin runs-on: ubuntu-20.04 temporarily while planning recompile.
|
|
59
|
+
ubuntu-20.04 runners will eventually be retired — plan a migration timeline.
|
|
60
|
+
fix_code:
|
|
61
|
+
- language: yaml
|
|
62
|
+
label: 'Install libssl1.1 compatibility shim from Ubuntu 20.04 focal archive (temporary workaround)'
|
|
63
|
+
code: |
|
|
64
|
+
jobs:
|
|
65
|
+
build:
|
|
66
|
+
runs-on: ubuntu-22.04
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v4
|
|
69
|
+
|
|
70
|
+
- name: Install libssl1.1 compatibility shim
|
|
71
|
+
run: |
|
|
72
|
+
wget -q http://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb
|
|
73
|
+
sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2_amd64.deb
|
|
74
|
+
# WARNING: Temporary workaround only. Recompile binary against OpenSSL 3.0 for the permanent fix.
|
|
75
|
+
|
|
76
|
+
- name: Run legacy binary
|
|
77
|
+
run: ./vendor/legacy-tool
|
|
78
|
+
|
|
79
|
+
- language: yaml
|
|
80
|
+
label: 'Use container job with ubuntu:20.04 base image to isolate libssl1.1 dependency'
|
|
81
|
+
code: |
|
|
82
|
+
jobs:
|
|
83
|
+
build:
|
|
84
|
+
runs-on: ubuntu-latest
|
|
85
|
+
container:
|
|
86
|
+
image: ubuntu:20.04
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@v4
|
|
89
|
+
|
|
90
|
+
- name: Install prerequisites
|
|
91
|
+
run: |
|
|
92
|
+
apt-get update
|
|
93
|
+
apt-get install -y libssl1.1 curl
|
|
94
|
+
|
|
95
|
+
- name: Run legacy binary
|
|
96
|
+
run: ./vendor/legacy-tool
|
|
97
|
+
# Binary now runs with libssl1.1 available in container
|
|
98
|
+
prevention:
|
|
99
|
+
- 'Compile release binaries against the same OpenSSL version as the target runner OS (3.0 for Ubuntu 22.04+)'
|
|
100
|
+
- 'When upgrading ubuntu-latest, audit all pre-compiled binaries with ldd to check for libssl.so.1.1 dynamic dependencies before rolling out'
|
|
101
|
+
- 'Use statically-linked binaries or distroless container images for tools that must run across multiple Ubuntu versions'
|
|
102
|
+
- 'In matrix workflows, include ubuntu-22.04 alongside ubuntu-20.04 to catch OpenSSL ABI incompatibilities before retirement deadlines'
|
|
103
|
+
docs:
|
|
104
|
+
- url: 'https://github.com/actions/runner-images/issues/6399'
|
|
105
|
+
label: 'runner-images#6399: libssl.so.1.1 missing on Ubuntu 22.04'
|
|
106
|
+
- url: 'https://wiki.openssl.org/index.php/OpenSSL_3.0'
|
|
107
|
+
label: 'OpenSSL 3.0 migration guide — ABI compatibility with OpenSSL 1.1'
|
|
108
|
+
- url: 'https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners'
|
|
109
|
+
label: 'GitHub Docs: About GitHub-hosted runners'
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
id: runner-environment-109
|
|
2
|
+
title: 'macOS 14+ (Apple Silicon) runner: Homebrew prefix changed from /usr/local to /opt/homebrew — hardcoded paths fail'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- macos-14
|
|
7
|
+
- apple-silicon
|
|
8
|
+
- homebrew
|
|
9
|
+
- arm64
|
|
10
|
+
- path
|
|
11
|
+
- macos-sonoma
|
|
12
|
+
- opt-homebrew
|
|
13
|
+
- cross-architecture
|
|
14
|
+
patterns:
|
|
15
|
+
- regex: '/usr/local/(bin/brew|Cellar|opt)\b'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'No such file or directory.*(/usr/local/bin/brew|/usr/local/opt|/usr/local/Cellar)'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- '/usr/local/bin/brew: No such file or directory'
|
|
21
|
+
- 'Error: No such file or directory @ rb_sysopen - /usr/local/Cellar'
|
|
22
|
+
- '/usr/local/opt/openssl: No such file or directory'
|
|
23
|
+
- 'pkg-config: /usr/local/opt/openssl/lib/pkgconfig: No such file or directory'
|
|
24
|
+
root_cause: |
|
|
25
|
+
Homebrew on Apple Silicon (ARM64) installs to /opt/homebrew, while Homebrew on
|
|
26
|
+
Intel x86-64 installs to /usr/local. This architectural split was introduced when
|
|
27
|
+
Homebrew added native Apple Silicon support in early 2021.
|
|
28
|
+
|
|
29
|
+
GitHub-hosted macOS runners on Apple Silicon hardware (macos-14, macos-15, and
|
|
30
|
+
macos-latest since October 2024) use /opt/homebrew as the Homebrew prefix.
|
|
31
|
+
Workflows migrating from macOS 12 or macOS 13 (Intel x86-64) to macOS 14+
|
|
32
|
+
(Apple Silicon) break when they reference hardcoded /usr/local paths:
|
|
33
|
+
|
|
34
|
+
- /usr/local/bin/brew — the brew binary itself
|
|
35
|
+
- /usr/local/Cellar/package-name — installed package files
|
|
36
|
+
- /usr/local/opt/package-name — formula options/keg link (very common for
|
|
37
|
+
openssl, readline, libpq, libyaml, icu4c, pkg-config)
|
|
38
|
+
- PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig in env: blocks
|
|
39
|
+
- OPENSSL_ROOT_DIR=/usr/local/opt/openssl in build steps
|
|
40
|
+
- LDFLAGS=-L/usr/local/opt/readline/lib for native extension builds
|
|
41
|
+
- CPPFLAGS=-I/usr/local/opt/openssl/include for C/C++ compilation
|
|
42
|
+
- Shell scripts that source /usr/local/etc/profile.d/
|
|
43
|
+
|
|
44
|
+
Commonly broken language ecosystems:
|
|
45
|
+
- Ruby gems with native extensions (nokogiri --with-opt-dir=/usr/local/opt/openssl)
|
|
46
|
+
- Python packages (pycurl, psycopg2, cryptography) with hardcoded CPPFLAGS
|
|
47
|
+
- Node.js native add-ons reading PKG_CONFIG_PATH pointing to /usr/local/opt
|
|
48
|
+
- Go builds with CGO_CFLAGS referencing /usr/local/include
|
|
49
|
+
fix: |
|
|
50
|
+
Replace all hardcoded /usr/local/opt and /usr/local/Cellar paths with the
|
|
51
|
+
dynamic output of brew --prefix [formula-name].
|
|
52
|
+
|
|
53
|
+
The portable pattern:
|
|
54
|
+
OPENSSL_ROOT_DIR=$(brew --prefix openssl)
|
|
55
|
+
PKG_CONFIG_PATH="$(brew --prefix openssl)/lib/pkgconfig"
|
|
56
|
+
|
|
57
|
+
brew --prefix returns /usr/local/opt/package on Intel and /opt/homebrew/opt/package
|
|
58
|
+
on Apple Silicon — making it correct on both architectures automatically.
|
|
59
|
+
|
|
60
|
+
For the Homebrew root itself, use the $HOMEBREW_PREFIX environment variable
|
|
61
|
+
which is pre-set by the runner image to the correct prefix (/usr/local or
|
|
62
|
+
/opt/homebrew) depending on the runner architecture.
|
|
63
|
+
|
|
64
|
+
Actionlint does not flag hardcoded /usr/local paths — a manual audit of
|
|
65
|
+
env: blocks and run: scripts is required when migrating runner architectures.
|
|
66
|
+
fix_code:
|
|
67
|
+
- language: yaml
|
|
68
|
+
label: 'Use brew --prefix for portable formula paths across Intel and Apple Silicon'
|
|
69
|
+
code: |
|
|
70
|
+
jobs:
|
|
71
|
+
build:
|
|
72
|
+
runs-on: macos-latest # macos-14+ on Apple Silicon (/opt/homebrew)
|
|
73
|
+
steps:
|
|
74
|
+
- uses: actions/checkout@v4
|
|
75
|
+
|
|
76
|
+
- name: Install dependencies
|
|
77
|
+
run: brew install openssl readline libpq
|
|
78
|
+
|
|
79
|
+
- name: Set library paths (portable — works on Intel and Apple Silicon)
|
|
80
|
+
run: |
|
|
81
|
+
echo "OPENSSL_ROOT_DIR=$(brew --prefix openssl)" >> $GITHUB_ENV
|
|
82
|
+
echo "PKG_CONFIG_PATH=$(brew --prefix openssl)/lib/pkgconfig:$(brew --prefix readline)/lib/pkgconfig" >> $GITHUB_ENV
|
|
83
|
+
echo "LDFLAGS=-L$(brew --prefix openssl)/lib -L$(brew --prefix readline)/lib" >> $GITHUB_ENV
|
|
84
|
+
echo "CPPFLAGS=-I$(brew --prefix openssl)/include -I$(brew --prefix readline)/include" >> $GITHUB_ENV
|
|
85
|
+
# DO NOT hardcode /usr/local/opt/... — breaks on Apple Silicon (/opt/homebrew)
|
|
86
|
+
|
|
87
|
+
- name: Install native gems
|
|
88
|
+
run: bundle install
|
|
89
|
+
|
|
90
|
+
- language: yaml
|
|
91
|
+
label: 'Use $HOMEBREW_PREFIX env variable for Homebrew root references'
|
|
92
|
+
code: |
|
|
93
|
+
jobs:
|
|
94
|
+
build:
|
|
95
|
+
runs-on: macos-latest
|
|
96
|
+
steps:
|
|
97
|
+
- uses: actions/checkout@v4
|
|
98
|
+
|
|
99
|
+
- name: Install tool
|
|
100
|
+
run: brew install libffi
|
|
101
|
+
|
|
102
|
+
- name: Build with native dependency
|
|
103
|
+
run: |
|
|
104
|
+
# $HOMEBREW_PREFIX is pre-set by the runner image
|
|
105
|
+
# Intel macOS 12/13: /usr/local
|
|
106
|
+
# Apple Silicon 14/15: /opt/homebrew
|
|
107
|
+
export LIBRARY_PATH="$HOMEBREW_PREFIX/lib:$LIBRARY_PATH"
|
|
108
|
+
export C_INCLUDE_PATH="$HOMEBREW_PREFIX/include:$C_INCLUDE_PATH"
|
|
109
|
+
make install
|
|
110
|
+
prevention:
|
|
111
|
+
- 'Never hardcode /usr/local/opt, /usr/local/Cellar, or /usr/local/bin/brew — always use brew --prefix <formula> at runtime'
|
|
112
|
+
- 'Use $HOMEBREW_PREFIX (set by runner images) for generic Homebrew root path references'
|
|
113
|
+
- 'When migrating from macos-12/13 (Intel) to macos-14+ (Apple Silicon), grep workflow files and scripts for /usr/local/ strings'
|
|
114
|
+
- 'Test with runs-on: macos-14 in a feature branch before switching macos-latest to catch architecture-specific path failures'
|
|
115
|
+
docs:
|
|
116
|
+
- url: 'https://docs.brew.sh/Installation'
|
|
117
|
+
label: 'Homebrew Installation docs: default prefix differences between Intel and Apple Silicon'
|
|
118
|
+
- url: 'https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md'
|
|
119
|
+
label: 'actions/runner-images: macOS 14 ARM64 image README'
|
|
120
|
+
- url: 'https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners'
|
|
121
|
+
label: 'GitHub Docs: About GitHub-hosted runners — macOS runner architecture details'
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
id: runner-environment-110
|
|
2
|
+
title: 'actions/checkout default fetch-depth:1 shallow clone breaks git describe and changelog generation'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- checkout
|
|
7
|
+
- fetch-depth
|
|
8
|
+
- shallow-clone
|
|
9
|
+
- git-describe
|
|
10
|
+
- versioning
|
|
11
|
+
- changelog
|
|
12
|
+
- tags
|
|
13
|
+
- release
|
|
14
|
+
patterns:
|
|
15
|
+
- regex: 'fatal:\s*No names found.*cannot describe|No tag can describe'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'fetch-depth:\s*[01]\b'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- 'fatal: No names found, cannot describe anything.'
|
|
21
|
+
- 'fatal: No tags can describe ''HEAD''.'
|
|
22
|
+
- 'error: No annotated tags can describe'
|
|
23
|
+
- '##[error]fatal: no tag exactly matches'
|
|
24
|
+
root_cause: |
|
|
25
|
+
actions/checkout defaults to fetch-depth: 1 — a shallow clone that downloads
|
|
26
|
+
only the single most recent commit. This optimizes CI download speed but breaks
|
|
27
|
+
any operation that requires commit history or reachable tag ancestry:
|
|
28
|
+
|
|
29
|
+
- git describe --tags: requires a tag somewhere in the commit ancestry chain.
|
|
30
|
+
With depth 1, no ancestor commits exist, so no tags are reachable unless the
|
|
31
|
+
HEAD commit IS exactly the tagged commit. Returns "fatal: No names found,
|
|
32
|
+
cannot describe anything."
|
|
33
|
+
|
|
34
|
+
- Changelog generators (conventional-changelog, release-please, git-cliff,
|
|
35
|
+
semantic-release): need commit history to determine what changed since the
|
|
36
|
+
last release. With depth 1, they see zero commits and produce empty changelogs.
|
|
37
|
+
|
|
38
|
+
- Semantic versioning tools that count commits since last tag always return 0
|
|
39
|
+
because no prior commits (and thus no tags) are in the shallow history.
|
|
40
|
+
|
|
41
|
+
- Branch comparison and merge-base operations: git merge-base fails or produces
|
|
42
|
+
incorrect results with no shared history available.
|
|
43
|
+
|
|
44
|
+
A common workaround attempt — using fetch-tags: true with fetch-depth: 1 —
|
|
45
|
+
fetches tag objects but NOT the commits they point to in history, so
|
|
46
|
+
git describe still fails. The tags exist as objects but are not reachable from
|
|
47
|
+
HEAD via the commit graph.
|
|
48
|
+
|
|
49
|
+
This is one of the most-discussed GitHub Community topics: the checkout step
|
|
50
|
+
looks correct, CI succeeds, but release version numbers or changelogs are
|
|
51
|
+
unexpectedly empty or wrong, often discovered only after a real release attempt.
|
|
52
|
+
fix: |
|
|
53
|
+
Set fetch-depth: 0 to fetch complete commit history including all tags:
|
|
54
|
+
|
|
55
|
+
- uses: actions/checkout@v4
|
|
56
|
+
with:
|
|
57
|
+
fetch-depth: 0
|
|
58
|
+
|
|
59
|
+
For large repositories where full history is prohibitively slow, use targeted
|
|
60
|
+
approaches:
|
|
61
|
+
- fetch-depth: 50 covers the last 50 commits — sufficient for active repos
|
|
62
|
+
that tag frequently, but risky for repos with infrequent tagging.
|
|
63
|
+
- fetch-depth: 0 combined with --filter=blob:none (partial clone) gets history
|
|
64
|
+
metadata without full file blobs — not directly configurable in the action but
|
|
65
|
+
achievable via fetch options in post-checkout scripts for advanced cases.
|
|
66
|
+
|
|
67
|
+
For changelog and release tools (release-please, git-cliff, semantic-release,
|
|
68
|
+
conventional-changelog), always use fetch-depth: 0. These tools explicitly
|
|
69
|
+
require full history and their documentation states this requirement.
|
|
70
|
+
fix_code:
|
|
71
|
+
- language: yaml
|
|
72
|
+
label: 'Full history checkout for git describe and changelog generation tools'
|
|
73
|
+
code: |
|
|
74
|
+
jobs:
|
|
75
|
+
release:
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/checkout@v4
|
|
79
|
+
with:
|
|
80
|
+
fetch-depth: 0 # Full history required for version detection and changelogs
|
|
81
|
+
# Default fetch-depth: 1 (shallow) causes "No names found, cannot describe"
|
|
82
|
+
|
|
83
|
+
- name: Generate changelog
|
|
84
|
+
uses: orhun/git-cliff-action@v3
|
|
85
|
+
with:
|
|
86
|
+
config: cliff.toml
|
|
87
|
+
|
|
88
|
+
- language: yaml
|
|
89
|
+
label: 'Partial depth with fetch-tags for large repos — use with caution'
|
|
90
|
+
code: |
|
|
91
|
+
jobs:
|
|
92
|
+
version:
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
steps:
|
|
95
|
+
- uses: actions/checkout@v4
|
|
96
|
+
with:
|
|
97
|
+
fetch-depth: 100 # Last 100 commits — sufficient for frequently-tagged repos
|
|
98
|
+
fetch-tags: true # Fetch tag objects so they appear in tag list
|
|
99
|
+
# WARNING: fetch-tags: true with a shallow clone fetches tag OBJECTS
|
|
100
|
+
# but NOT their commit ancestors — git describe may still fail if
|
|
101
|
+
# the tag is older than fetch-depth commits. Use fetch-depth: 0 to be safe.
|
|
102
|
+
prevention:
|
|
103
|
+
- 'Set fetch-depth: 0 in any workflow that uses git describe, generates a changelog, or computes a semantic version from commit history'
|
|
104
|
+
- 'Document in your workflow why fetch-depth: 0 is required so future maintainers do not revert it to the shallow default'
|
|
105
|
+
- 'Do not assume fetch-tags: true solves shallow clone limitations for git describe — the tag objects must be reachable in the commit graph'
|
|
106
|
+
- 'Keep the default fetch-depth: 1 only for workflows that never need commit history — pure build and test jobs with no versioning or history analysis'
|
|
107
|
+
docs:
|
|
108
|
+
- url: 'https://github.com/actions/checkout#usage'
|
|
109
|
+
label: 'actions/checkout: fetch-depth parameter documentation'
|
|
110
|
+
- url: 'https://github.com/orgs/community/discussions/25702'
|
|
111
|
+
label: 'GitHub Community: git describe fails with actions/checkout shallow clone'
|
|
112
|
+
- url: 'https://github.com/actions/checkout/issues/701'
|
|
113
|
+
label: 'actions/checkout #701: fetch-tags does not help with git describe on shallow clone'
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
id: silent-failures-053
|
|
2
|
+
title: 'workflow_dispatch boolean inputs arrive as strings — if: inputs.flag is always truthy'
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow-dispatch
|
|
7
|
+
- boolean-inputs
|
|
8
|
+
- type-coercion
|
|
9
|
+
- inputs-context
|
|
10
|
+
- string-comparison
|
|
11
|
+
- if-condition
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'inputs\.\w+\s*==\s*true\b'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'if:\s*inputs\.\w+'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- 'Step always runs even when boolean input is set to false'
|
|
19
|
+
- 'Boolean workflow_dispatch input behaves as truthy regardless of value'
|
|
20
|
+
root_cause: |
|
|
21
|
+
GitHub Actions workflow_dispatch inputs defined with type: boolean are passed
|
|
22
|
+
to the workflow as strings ("true" or "false"), not as JavaScript/YAML booleans.
|
|
23
|
+
|
|
24
|
+
This means three common patterns silently misbehave:
|
|
25
|
+
- if: inputs.dry_run evaluates to always truthy (non-empty string)
|
|
26
|
+
- if: inputs.dry_run == true always false (string "true" != boolean true)
|
|
27
|
+
- if: inputs.dry_run == false always false (string "false" != boolean false)
|
|
28
|
+
|
|
29
|
+
The GitHub Actions expression evaluator converts the inputs context to strings
|
|
30
|
+
at the boundary between the event payload and the workflow context. Even though
|
|
31
|
+
the UI renders a checkbox, the underlying value is the literal string "true" or
|
|
32
|
+
"false". This behavior is consistent across workflow_dispatch and workflow_call
|
|
33
|
+
inputs of type boolean.
|
|
34
|
+
|
|
35
|
+
GitHub Community discussion #51504 documents this as expected behavior, but
|
|
36
|
+
thousands of developers are surprised by the mismatch between the checkbox UI
|
|
37
|
+
and the string runtime value.
|
|
38
|
+
|
|
39
|
+
Affected patterns:
|
|
40
|
+
- if: inputs.dry_run (truthy check — always true for any non-empty string value)
|
|
41
|
+
- if: inputs.flag == true (boolean comparison — always false for string "true")
|
|
42
|
+
- Ternary: ${{ inputs.flag && 'yes' || 'no' }} returns "yes" even when flag is "false"
|
|
43
|
+
fix: |
|
|
44
|
+
Always compare workflow_dispatch boolean inputs to the string literal 'true'
|
|
45
|
+
or 'false':
|
|
46
|
+
|
|
47
|
+
- if: inputs.dry_run == 'true' correct
|
|
48
|
+
- if: inputs.dry_run != 'true' correct negation (catches "false" and "")
|
|
49
|
+
- if: inputs.dry_run == true WRONG — string never equals boolean
|
|
50
|
+
- if: inputs.dry_run WRONG — always truthy for non-empty string
|
|
51
|
+
|
|
52
|
+
For workflow_call with boolean inputs, the same string coercion applies when
|
|
53
|
+
the calling workflow passes the value via the inputs: block.
|
|
54
|
+
|
|
55
|
+
The fromJSON() function converts the string to a real boolean if needed for
|
|
56
|
+
complex conditions: fromJSON(inputs.dry_run) returns actual true/false booleans.
|
|
57
|
+
fix_code:
|
|
58
|
+
- language: yaml
|
|
59
|
+
label: 'Correct boolean input comparison using string equality'
|
|
60
|
+
code: |
|
|
61
|
+
on:
|
|
62
|
+
workflow_dispatch:
|
|
63
|
+
inputs:
|
|
64
|
+
dry_run:
|
|
65
|
+
description: 'Run without making changes'
|
|
66
|
+
type: boolean
|
|
67
|
+
default: false
|
|
68
|
+
|
|
69
|
+
jobs:
|
|
70
|
+
deploy:
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
steps:
|
|
73
|
+
- name: Deploy (skip if dry run)
|
|
74
|
+
# WRONG: if: inputs.dry_run -- always truthy (non-empty string)
|
|
75
|
+
# WRONG: if: inputs.dry_run == true -- string never equals boolean
|
|
76
|
+
if: inputs.dry_run != 'true'
|
|
77
|
+
run: echo "Deploying..."
|
|
78
|
+
|
|
79
|
+
- name: Dry run notice
|
|
80
|
+
if: inputs.dry_run == 'true'
|
|
81
|
+
run: echo "Dry run mode — no changes made"
|
|
82
|
+
|
|
83
|
+
- language: yaml
|
|
84
|
+
label: 'Use fromJSON() to convert to real boolean for complex conditions'
|
|
85
|
+
code: |
|
|
86
|
+
jobs:
|
|
87
|
+
build:
|
|
88
|
+
runs-on: ubuntu-latest
|
|
89
|
+
steps:
|
|
90
|
+
- name: Conditional step using fromJSON conversion
|
|
91
|
+
if: fromJSON(inputs.dry_run) == false
|
|
92
|
+
run: echo "Not a dry run — proceeding"
|
|
93
|
+
|
|
94
|
+
- name: Set environment variable from boolean input
|
|
95
|
+
env:
|
|
96
|
+
# fromJSON converts "true"/"false" string to actual boolean
|
|
97
|
+
DRY_RUN: ${{ fromJSON(inputs.dry_run) }}
|
|
98
|
+
run: |
|
|
99
|
+
echo "Dry run mode: $DRY_RUN"
|
|
100
|
+
prevention:
|
|
101
|
+
- 'Always compare workflow_dispatch boolean inputs to the string literal ''true'' or ''false'', not to boolean true/false'
|
|
102
|
+
- 'Never use ''if: inputs.flag'' alone as a boolean check — the non-empty string "false" is truthy in GitHub Actions expressions'
|
|
103
|
+
- 'Use fromJSON(inputs.flag) when you need a real boolean value in expressions or env: context'
|
|
104
|
+
- 'Add a comment in your workflow noting that boolean dispatch inputs are strings at runtime to warn future maintainers'
|
|
105
|
+
docs:
|
|
106
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-syntax-for-github-actions#onworkflow_dispatchinputs'
|
|
107
|
+
label: 'GitHub Docs: workflow_dispatch inputs syntax'
|
|
108
|
+
- url: 'https://github.com/orgs/community/discussions/51504'
|
|
109
|
+
label: 'GitHub Community #51504: Boolean inputs in workflow_dispatch are strings'
|
|
110
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#fromjson'
|
|
111
|
+
label: 'GitHub Docs: fromJSON expression function'
|
package/package.json
CHANGED