@htekdev/actions-debugger 1.0.119 → 1.0.121
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-071.yml +114 -0
- package/errors/concurrency-timing/concurrency-timing-058.yml +121 -0
- package/errors/known-unsolved/known-unsolved-069.yml +127 -0
- package/errors/permissions-auth/permissions-auth-070.yml +136 -0
- package/errors/runner-environment/runner-environment-219.yml +120 -0
- package/errors/runner-environment/runner-environment-220.yml +123 -0
- package/package.json +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
id: caching-artifacts-071
|
|
2
|
+
title: 'actions/cache Fails on Self-Hosted Runner with IPv6 Cache Server URL'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- ipv6
|
|
8
|
+
- self-hosted
|
|
9
|
+
- getaddrinfo
|
|
10
|
+
- ENOTFOUND
|
|
11
|
+
- internal-cache
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'getaddrinfo ENOTFOUND \[[\da-fA-F:]+\]'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'getCacheEntry failed.*getaddrinfo ENOTFOUND'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'reserveCache failed.*getaddrinfo ENOTFOUND.*\['
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- "::warning::Failed to restore: getCacheEntry failed: getaddrinfo ENOTFOUND [2001:bc8:1d90:1fc1:dc00:ff:fe2b:3f97]"
|
|
21
|
+
- "::warning::Failed to save: reserveCache failed: getaddrinfo ENOTFOUND [2001:db8::1]"
|
|
22
|
+
root_cause: |
|
|
23
|
+
When GitHub Actions self-hosted runner infrastructure uses an IPv6 address for
|
|
24
|
+
the internal cache service URL (ACTIONS_CACHE_URL), the @actions/http-client
|
|
25
|
+
in the toolkit fails to connect because it treats the bracketed IPv6 literal
|
|
26
|
+
as a hostname string instead of an address.
|
|
27
|
+
|
|
28
|
+
DNS resolution of `[2001:bc8:...]` fails with ENOTFOUND because the brackets
|
|
29
|
+
are IANA-standard URL notation for IPv6 literals in HTTP URLs
|
|
30
|
+
(RFC 2732 / RFC 3986 section 3.2.2), but the toolkit's Node.js http-client
|
|
31
|
+
was passing the bracketed string directly to `getaddrinfo()` without stripping
|
|
32
|
+
the brackets first. getaddrinfo does not accept bracket-wrapped IPv6 literals
|
|
33
|
+
and returns ENOTFOUND.
|
|
34
|
+
|
|
35
|
+
This affects any self-hosted runner environment where:
|
|
36
|
+
- The cache service is configured with an IPv6 address directly in the URL
|
|
37
|
+
(e.g., ACTIONS_CACHE_URL=http://[2001:bc8::1]:3000/)
|
|
38
|
+
- This includes ARC (Actions Runner Controller) on IPv6-only Kubernetes clusters,
|
|
39
|
+
enterprise GitHub Actions server deployments, and any on-prem cache proxy
|
|
40
|
+
bound to an IPv6 interface
|
|
41
|
+
|
|
42
|
+
The fix (strip brackets before passing to http-client) was submitted as PR
|
|
43
|
+
actions/toolkit#2298. Until merged and released in a new version of actions/cache,
|
|
44
|
+
the bug is present in actions/cache@v3, v4, and v5.
|
|
45
|
+
fix: |
|
|
46
|
+
Short-term workarounds (choose one):
|
|
47
|
+
|
|
48
|
+
1. Configure the internal cache service to also listen on an IPv4 address or
|
|
49
|
+
hostname and set ACTIONS_CACHE_URL to use the IPv4 address or hostname:
|
|
50
|
+
export ACTIONS_CACHE_URL=http://cache.internal:3000/
|
|
51
|
+
export ACTIONS_CACHE_URL=http://192.168.1.10:3000/
|
|
52
|
+
|
|
53
|
+
2. Set ACTIONS_CACHE_URL to use the hostname that resolves to the IPv6 address
|
|
54
|
+
(DNS-based resolution of the hostname works; the issue is only with bare
|
|
55
|
+
IPv6 literals in brackets):
|
|
56
|
+
export ACTIONS_CACHE_URL=http://cache-server.local:3000/
|
|
57
|
+
|
|
58
|
+
3. Add the cache service IPv6 address to /etc/hosts with a hostname alias on
|
|
59
|
+
the runner and use that hostname in ACTIONS_CACHE_URL.
|
|
60
|
+
|
|
61
|
+
Long-term: Track actions/toolkit#2298 for the upstream fix. Once merged and
|
|
62
|
+
actions/cache releases a new version using the patched toolkit, upgrade to
|
|
63
|
+
that version.
|
|
64
|
+
fix_code:
|
|
65
|
+
- language: yaml
|
|
66
|
+
label: 'Broken — ACTIONS_CACHE_URL with IPv6 literal causes ENOTFOUND'
|
|
67
|
+
code: |
|
|
68
|
+
# Runner environment configuration (runsvc.sh or .env file):
|
|
69
|
+
# ACTIONS_CACHE_URL=http://[2001:bc8:1d90:1fc1:dc00:ff:fe2b:3f97]:3000/
|
|
70
|
+
#
|
|
71
|
+
# Workflow sees:
|
|
72
|
+
# ::warning::Failed to restore: getCacheEntry failed: getaddrinfo ENOTFOUND [2001:bc8:...]
|
|
73
|
+
|
|
74
|
+
- uses: actions/cache@v5
|
|
75
|
+
with:
|
|
76
|
+
path: ~/.npm
|
|
77
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
|
78
|
+
# Warning: cache restore/save fails silently with ENOTFOUND
|
|
79
|
+
|
|
80
|
+
- language: yaml
|
|
81
|
+
label: 'Fixed — set ACTIONS_CACHE_URL to hostname or IPv4 instead of IPv6 literal'
|
|
82
|
+
code: |
|
|
83
|
+
# Runner environment configuration — use hostname instead of IPv6 literal:
|
|
84
|
+
# ACTIONS_CACHE_URL=http://cache.internal:3000/
|
|
85
|
+
# -or-
|
|
86
|
+
# ACTIONS_CACHE_URL=http://192.168.1.10:3000/
|
|
87
|
+
#
|
|
88
|
+
# The cache action now resolves the hostname normally:
|
|
89
|
+
- uses: actions/cache@v5
|
|
90
|
+
with:
|
|
91
|
+
path: ~/.npm
|
|
92
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
|
93
|
+
|
|
94
|
+
- language: yaml
|
|
95
|
+
label: 'Diagnostic — detect IPv6 ENOTFOUND cache failures in workflow output'
|
|
96
|
+
code: |
|
|
97
|
+
- name: Check cache URL for IPv6 literal
|
|
98
|
+
run: |
|
|
99
|
+
if [[ "${ACTIONS_CACHE_URL:-}" =~ ^\[.*\] ]] || [[ "${ACTIONS_CACHE_URL:-}" == *"["* ]]; then
|
|
100
|
+
echo "::warning::ACTIONS_CACHE_URL contains an IPv6 literal: ${ACTIONS_CACHE_URL}"
|
|
101
|
+
echo "::warning::Cache actions will fail with ENOTFOUND. Use a hostname or IPv4 address instead."
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
prevention:
|
|
105
|
+
- 'Always configure ACTIONS_CACHE_URL with a hostname or IPv4 address, not a bare IPv6 literal in brackets.'
|
|
106
|
+
- 'When deploying ARC or enterprise runner infrastructure on IPv6-only Kubernetes nodes, set up a hostname alias for the cache service.'
|
|
107
|
+
- 'Test cache operations with a simple cache-hit workflow on a new self-hosted runner before deploying to production.'
|
|
108
|
+
docs:
|
|
109
|
+
- url: 'https://github.com/actions/cache/issues/1718'
|
|
110
|
+
label: 'actions/cache#1718 — Caching fails on IPv6 cache server'
|
|
111
|
+
- url: 'https://github.com/actions/toolkit/pull/2298'
|
|
112
|
+
label: 'actions/toolkit#2298 — Fix: correctly handle IPv6 literals in http-client'
|
|
113
|
+
- url: 'https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2'
|
|
114
|
+
label: 'RFC 3986 §3.2.2 — IPv6 literals in URL host component (bracket notation)'
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
id: concurrency-timing-058
|
|
2
|
+
title: "concurrency queue:max 100-Slot Overflow Silently Cancels the Oldest Pending Run"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- concurrency
|
|
7
|
+
- queue-max
|
|
8
|
+
- silent-cancel
|
|
9
|
+
- pending
|
|
10
|
+
- overflow
|
|
11
|
+
- deployment
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'queue.*max|queue:.*max'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'This run was cancelled|run.*cancelled'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "This run was cancelled."
|
|
19
|
+
- "Run was cancelled."
|
|
20
|
+
root_cause: |
|
|
21
|
+
GitHub Actions concurrency queue:max allows up to 100 workflow runs or jobs
|
|
22
|
+
to wait in a single concurrency group instead of being cancelled immediately.
|
|
23
|
+
When the 100-slot queue is already full and a new run arrives, the oldest
|
|
24
|
+
pending run in the queue is silently cancelled to make room.
|
|
25
|
+
|
|
26
|
+
The cancelled run shows "This run was cancelled." in the UI with no
|
|
27
|
+
indication that the cancellation was caused by queue overflow. This message
|
|
28
|
+
is identical to normal concurrency cancel-in-progress cancellations, making
|
|
29
|
+
it impossible to distinguish overflow cancellation from intentional
|
|
30
|
+
cancellation without additional observability.
|
|
31
|
+
|
|
32
|
+
This problem surfaces when:
|
|
33
|
+
- A pipeline is slower than the push rate (runs queue faster than they drain)
|
|
34
|
+
- A burst of commits or tags fires many workflow runs simultaneously
|
|
35
|
+
- A monorepo path filter causes many unrelated commits to all queue in the
|
|
36
|
+
same deployment concurrency group
|
|
37
|
+
- A workflow is accidentally triggered on every push to any branch and all
|
|
38
|
+
share a single production-deploy concurrency group
|
|
39
|
+
|
|
40
|
+
Note: queue:max and cancel-in-progress:true cannot be combined (validation
|
|
41
|
+
error). queue:max assumes you want all runs to eventually execute in order.
|
|
42
|
+
fix: |
|
|
43
|
+
queue:max with 100 slots is generous for most pipelines. If you are hitting
|
|
44
|
+
the overflow limit, the queue depth is a symptom of a throughput mismatch:
|
|
45
|
+
|
|
46
|
+
1. Speed up the job so the queue drains faster than it fills. Profile and
|
|
47
|
+
optimize the slowest steps (build caching, parallelism inside the job).
|
|
48
|
+
|
|
49
|
+
2. Narrow the trigger to reduce unnecessary queuing:
|
|
50
|
+
- Use paths: filters so only relevant changes trigger the workflow
|
|
51
|
+
- Only trigger on specific branches (main, release/*) not all branches
|
|
52
|
+
- Use workflow_dispatch for on-demand deploys instead of automatic pushes
|
|
53
|
+
|
|
54
|
+
3. Split into multiple concurrency groups scoped per environment or service
|
|
55
|
+
component. Each group has its own 100-slot limit, distributing capacity
|
|
56
|
+
across workloads.
|
|
57
|
+
|
|
58
|
+
4. If strict ordering is not required and drops are acceptable, switch back
|
|
59
|
+
to queue:single (the default) with cancel-in-progress:false so only one
|
|
60
|
+
pending run is kept, avoiding the 100-slot limit entirely.
|
|
61
|
+
fix_code:
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: "queue:max — silently cancels oldest pending run when >100 queued"
|
|
64
|
+
code: |
|
|
65
|
+
on:
|
|
66
|
+
push:
|
|
67
|
+
branches: ['**'] # Triggers on every push to every branch
|
|
68
|
+
|
|
69
|
+
concurrency:
|
|
70
|
+
group: production-deploy # All branches share ONE slot — queue fills fast
|
|
71
|
+
queue: max # Up to 100 queued; 101st silently cancels the 1st
|
|
72
|
+
|
|
73
|
+
- language: yaml
|
|
74
|
+
label: "Fixed — scope concurrency group per environment + narrow push trigger"
|
|
75
|
+
code: |
|
|
76
|
+
on:
|
|
77
|
+
push:
|
|
78
|
+
branches:
|
|
79
|
+
- main
|
|
80
|
+
- 'release/**'
|
|
81
|
+
|
|
82
|
+
concurrency:
|
|
83
|
+
# Each environment gets its own 100-slot queue
|
|
84
|
+
group: deploy-${{ github.event.deployment.environment || 'staging' }}-${{ github.ref_name }}
|
|
85
|
+
queue: max
|
|
86
|
+
|
|
87
|
+
- language: yaml
|
|
88
|
+
label: "Fixed — add observability step to detect queue overflow"
|
|
89
|
+
code: |
|
|
90
|
+
jobs:
|
|
91
|
+
deploy:
|
|
92
|
+
runs-on: ubuntu-latest
|
|
93
|
+
concurrency:
|
|
94
|
+
group: production-deploy
|
|
95
|
+
queue: max
|
|
96
|
+
steps:
|
|
97
|
+
- name: Check concurrency queue depth
|
|
98
|
+
env:
|
|
99
|
+
GH_TOKEN: ${{ github.token }}
|
|
100
|
+
run: |
|
|
101
|
+
PENDING=$(gh api \
|
|
102
|
+
"repos/${{ github.repository }}/actions/runs?status=waiting&per_page=100" \
|
|
103
|
+
--jq '[.workflow_runs[] | select(.name == "${{ github.workflow }}")] | length')
|
|
104
|
+
echo "Runs currently waiting in concurrency queue: $PENDING"
|
|
105
|
+
if [ "${PENDING:-0}" -ge 90 ]; then
|
|
106
|
+
echo "::warning::Queue near capacity (${PENDING}/100). Oldest run may be dropped on next push."
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
- name: Deploy
|
|
110
|
+
run: ./deploy.sh
|
|
111
|
+
prevention:
|
|
112
|
+
- "Monitor pipeline throughput: if runs consistently back up to 50+ in queue, the job is too slow for the push frequency."
|
|
113
|
+
- "Narrow workflow triggers with paths: and branches: filters to reduce unnecessary queuing."
|
|
114
|
+
- "Scope concurrency groups per environment or service to distribute the 100-slot limit across workloads."
|
|
115
|
+
- "Use queue:max only when strict ordering matters (deployments). For CI checks, cancel-in-progress:true is preferable."
|
|
116
|
+
- "The combination queue:max and cancel-in-progress:true is a workflow validation error — do not use both."
|
|
117
|
+
docs:
|
|
118
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-concurrency"
|
|
119
|
+
label: "GitHub Docs — Using concurrency (queue:max behavior and 100-slot limit)"
|
|
120
|
+
- url: "https://docs.github.com/en/actions/reference/limits"
|
|
121
|
+
label: "GitHub Actions Limits — Concurrency group queue: 100 runs per group"
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
id: known-unsolved-069
|
|
2
|
+
title: "Matrix Strategy Hard Limit: 256 Jobs Per Workflow Run"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- matrix
|
|
7
|
+
- strategy
|
|
8
|
+
- job-limit
|
|
9
|
+
- dynamic-matrix
|
|
10
|
+
- fromjson
|
|
11
|
+
- scalability
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'matrix.*generates.*too many|too many.*entries.*matrix'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'limited to 256|exceeding.*allowed maximum.*256|256.*jobs.*per.*workflow'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Matrix generates too many entries. Limited to 256."
|
|
19
|
+
root_cause: |
|
|
20
|
+
GitHub Actions enforces a hard limit of 256 jobs per workflow run across
|
|
21
|
+
all matrix dimensions. This is calculated as the total Cartesian product of
|
|
22
|
+
all matrix axes after applying include/exclude entries.
|
|
23
|
+
|
|
24
|
+
Common triggers:
|
|
25
|
+
- Dynamic matrices from fromJSON output that grow over time as new targets
|
|
26
|
+
are added to a test matrix config file
|
|
27
|
+
- Multi-dimensional matrices (os x language x version) where the product
|
|
28
|
+
of all axis sizes exceeds 256
|
|
29
|
+
- Adding include: entries on top of an already-large base matrix, each
|
|
30
|
+
include: adds one additional job that counts against the 256 limit
|
|
31
|
+
- Monorepo CI matrices that test all services x all supported runtimes
|
|
32
|
+
|
|
33
|
+
The error fires immediately when the workflow is queued and prevents any
|
|
34
|
+
jobs from running. GitHub reports the total generated count in the error
|
|
35
|
+
message: "Matrix generates N entries. Limited to 256."
|
|
36
|
+
|
|
37
|
+
This limit applies equally to GitHub-hosted and self-hosted runners and
|
|
38
|
+
cannot be increased by contacting support.
|
|
39
|
+
fix: |
|
|
40
|
+
The 256-job-per-run limit is hard and cannot be increased. Options:
|
|
41
|
+
|
|
42
|
+
1. Split across multiple workflow files, each handling a subset of targets.
|
|
43
|
+
Trigger them from a parent coordinator workflow or via separate triggers.
|
|
44
|
+
|
|
45
|
+
2. Reduce matrix dimensions: test only meaningful combinations using
|
|
46
|
+
explicit include: lists rather than full Cartesian products. Not every
|
|
47
|
+
OS x language x version triplet needs to be tested.
|
|
48
|
+
|
|
49
|
+
3. Chunk using workflow_dispatch fan-out: a generator job creates N chunks
|
|
50
|
+
of at most 256 items and dispatches sub-workflow runs per chunk via
|
|
51
|
+
repository_dispatch or workflow_dispatch API calls.
|
|
52
|
+
|
|
53
|
+
4. Use a single job with a shell loop for some dimensions: instead of
|
|
54
|
+
separate matrix jobs per version, loop over versions inside one job step.
|
|
55
|
+
Sacrifices parallelism but avoids the limit entirely.
|
|
56
|
+
fix_code:
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: "Broken — Cartesian product silently grows past 256 as versions are added"
|
|
59
|
+
code: |
|
|
60
|
+
strategy:
|
|
61
|
+
matrix:
|
|
62
|
+
os: [ubuntu-22.04, ubuntu-24.04, windows-2022, macos-14, macos-15]
|
|
63
|
+
node: [18, 20, 22, 23]
|
|
64
|
+
python: ['3.9', '3.10', '3.11', '3.12', '3.13']
|
|
65
|
+
# 5 x 4 x 5 = 100 jobs today — fine, but adding node 24 pushes to 125,
|
|
66
|
+
# adding python 3.14 pushes to 150, and so on until 256 is silently hit
|
|
67
|
+
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: "Fixed — explicit include list tests only meaningful combinations"
|
|
70
|
+
code: |
|
|
71
|
+
strategy:
|
|
72
|
+
matrix:
|
|
73
|
+
include:
|
|
74
|
+
# Test LTS node on primary OS with latest stable Python
|
|
75
|
+
- os: ubuntu-24.04
|
|
76
|
+
node: 20
|
|
77
|
+
python: '3.12'
|
|
78
|
+
- os: ubuntu-24.04
|
|
79
|
+
node: 22
|
|
80
|
+
python: '3.12'
|
|
81
|
+
# Windows only gets the most recent LTS
|
|
82
|
+
- os: windows-2022
|
|
83
|
+
node: 20
|
|
84
|
+
python: '3.11'
|
|
85
|
+
# macOS gets one combination
|
|
86
|
+
- os: macos-15
|
|
87
|
+
node: 20
|
|
88
|
+
python: '3.12'
|
|
89
|
+
# Total: 4 jobs instead of 100+ from full Cartesian product
|
|
90
|
+
|
|
91
|
+
- language: yaml
|
|
92
|
+
label: "Defensive — validate dynamic matrix size before passing to strategy"
|
|
93
|
+
code: |
|
|
94
|
+
jobs:
|
|
95
|
+
generate-matrix:
|
|
96
|
+
runs-on: ubuntu-latest
|
|
97
|
+
outputs:
|
|
98
|
+
matrix: ${{ steps.gen.outputs.matrix }}
|
|
99
|
+
steps:
|
|
100
|
+
- id: gen
|
|
101
|
+
run: |
|
|
102
|
+
MATRIX=$(cat test-matrix.json)
|
|
103
|
+
COUNT=$(echo "$MATRIX" | jq '.include | length')
|
|
104
|
+
if [ "$COUNT" -gt 256 ]; then
|
|
105
|
+
echo "::error::Matrix has $COUNT entries (limit: 256). Split into multiple workflows."
|
|
106
|
+
exit 1
|
|
107
|
+
fi
|
|
108
|
+
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
|
|
109
|
+
|
|
110
|
+
test:
|
|
111
|
+
needs: generate-matrix
|
|
112
|
+
strategy:
|
|
113
|
+
matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }}
|
|
114
|
+
runs-on: ${{ matrix.os }}
|
|
115
|
+
steps:
|
|
116
|
+
- run: echo "Testing ${{ matrix.os }} / ${{ matrix.version }}"
|
|
117
|
+
prevention:
|
|
118
|
+
- "Before adding a new matrix axis, calculate the new total job count — it must stay at or below 256."
|
|
119
|
+
- "For dynamic matrices (fromJSON), validate the output list length in the generator job and fail fast if > 256."
|
|
120
|
+
- "Prefer explicit include: lists over full Cartesian products when not all dimension combinations are meaningful."
|
|
121
|
+
- "Monitor matrix job counts over time: a previously-safe matrix silently crosses 256 as new versions are added."
|
|
122
|
+
- "Document the current job count in a comment next to the matrix block so reviewers notice when PRs push it higher."
|
|
123
|
+
docs:
|
|
124
|
+
- url: "https://docs.github.com/en/actions/reference/limits"
|
|
125
|
+
label: "GitHub Actions Limits — Job Matrix: 256 jobs per workflow run"
|
|
126
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow"
|
|
127
|
+
label: "GitHub Docs — Using a matrix for your jobs"
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
id: permissions-auth-070
|
|
2
|
+
title: "GITHUB_TOKEN packages:write Cannot Delete GitHub Container Registry Packages"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- github-packages
|
|
7
|
+
- ghcr
|
|
8
|
+
- container-registry
|
|
9
|
+
- packages-delete
|
|
10
|
+
- permissions
|
|
11
|
+
- pat
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'delete.*package.*403|package.*delete.*forbidden'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'Resource not accessible by integration'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Resource not accessible by integration"
|
|
19
|
+
- "HttpError: Resource not accessible by integration"
|
|
20
|
+
- "403 Forbidden"
|
|
21
|
+
root_cause: |
|
|
22
|
+
GitHub Container Registry (ghcr.io) and other granular-permission package
|
|
23
|
+
registries (npm, NuGet, RubyGems on ghpr) use package-level access control
|
|
24
|
+
that is separate from repository permissions. The GITHUB_TOKEN workflow
|
|
25
|
+
permission block supports packages: write, which grants the ability to:
|
|
26
|
+
- Publish (push) new package versions
|
|
27
|
+
- Update package metadata and visibility
|
|
28
|
+
|
|
29
|
+
However, packages: write does NOT grant the ability to DELETE package
|
|
30
|
+
versions. Deletion of granular-permission packages requires the delete:packages
|
|
31
|
+
scope, which exists only on classic personal access tokens (PATs) — there is
|
|
32
|
+
no equivalent permission in the GITHUB_TOKEN fine-grained model.
|
|
33
|
+
|
|
34
|
+
As a result, workflows that use actions/delete-package-versions (or make
|
|
35
|
+
direct REST API calls to DELETE /user/packages/{type}/{name}/versions/{id}
|
|
36
|
+
or DELETE /orgs/{org}/packages/{type}/{package_name}/versions/{id}) always
|
|
37
|
+
fail with 403 "Resource not accessible by integration" even with:
|
|
38
|
+
permissions:
|
|
39
|
+
packages: write
|
|
40
|
+
|
|
41
|
+
Note: Repository-scoped packages (Apache Maven, legacy Gradle) that inherit
|
|
42
|
+
repository permissions may behave differently — deletion can work via the
|
|
43
|
+
repo admin role. Container Registry packages always use granular permissions.
|
|
44
|
+
fix: |
|
|
45
|
+
Replace GITHUB_TOKEN with a token that carries delete:packages authority.
|
|
46
|
+
|
|
47
|
+
Option 1 (recommended for orgs): GitHub App token
|
|
48
|
+
- Create a GitHub App with "Packages: Read & Write" and "Packages: Admin"
|
|
49
|
+
permissions
|
|
50
|
+
- Install the app on the org or repo
|
|
51
|
+
- Use actions/create-github-app-token to generate a token in the workflow
|
|
52
|
+
- Pass the token to the deletion step
|
|
53
|
+
|
|
54
|
+
Option 2 (simpler for personal/small-team repos): Classic PAT
|
|
55
|
+
- Generate a classic PAT (Settings > Developer settings > Personal access
|
|
56
|
+
tokens > Tokens (classic)) with the delete:packages scope
|
|
57
|
+
- Store as a repository or organization secret
|
|
58
|
+
- Reference the secret in the deletion step
|
|
59
|
+
|
|
60
|
+
Option 3 (repo-scoped packages only — Apache Maven, Gradle):
|
|
61
|
+
- Ensure the package uses repository-scoped permissions (not granular)
|
|
62
|
+
- Grant the workflow admin access to the repository
|
|
63
|
+
- This does NOT apply to ghcr.io container images, which always require
|
|
64
|
+
a PAT or App approach for deletion
|
|
65
|
+
fix_code:
|
|
66
|
+
- language: yaml
|
|
67
|
+
label: "Broken — GITHUB_TOKEN packages:write cannot delete container images"
|
|
68
|
+
code: |
|
|
69
|
+
permissions:
|
|
70
|
+
packages: write # Grants publish access but NOT delete access
|
|
71
|
+
|
|
72
|
+
jobs:
|
|
73
|
+
cleanup:
|
|
74
|
+
runs-on: ubuntu-latest
|
|
75
|
+
steps:
|
|
76
|
+
- name: Delete old versions
|
|
77
|
+
uses: actions/delete-package-versions@v5
|
|
78
|
+
with:
|
|
79
|
+
package-name: my-image
|
|
80
|
+
package-type: container
|
|
81
|
+
min-versions-to-keep: 5
|
|
82
|
+
token: ${{ secrets.GITHUB_TOKEN }} # Fails with 403 for container packages
|
|
83
|
+
|
|
84
|
+
- language: yaml
|
|
85
|
+
label: "Fixed — classic PAT with delete:packages scope"
|
|
86
|
+
code: |
|
|
87
|
+
permissions:
|
|
88
|
+
packages: write
|
|
89
|
+
|
|
90
|
+
jobs:
|
|
91
|
+
cleanup:
|
|
92
|
+
runs-on: ubuntu-latest
|
|
93
|
+
steps:
|
|
94
|
+
- name: Delete old versions
|
|
95
|
+
uses: actions/delete-package-versions@v5
|
|
96
|
+
with:
|
|
97
|
+
package-name: my-image
|
|
98
|
+
package-type: container
|
|
99
|
+
min-versions-to-keep: 5
|
|
100
|
+
# Classic PAT must have: write:packages + delete:packages scopes
|
|
101
|
+
token: ${{ secrets.PAT_DELETE_PACKAGES }}
|
|
102
|
+
|
|
103
|
+
- language: yaml
|
|
104
|
+
label: "Fixed — GitHub App token (recommended for organizations)"
|
|
105
|
+
code: |
|
|
106
|
+
jobs:
|
|
107
|
+
cleanup:
|
|
108
|
+
runs-on: ubuntu-latest
|
|
109
|
+
steps:
|
|
110
|
+
- name: Generate GitHub App token
|
|
111
|
+
id: app-token
|
|
112
|
+
uses: actions/create-github-app-token@v1
|
|
113
|
+
with:
|
|
114
|
+
app-id: ${{ vars.PACKAGE_CLEANUP_APP_ID }}
|
|
115
|
+
private-key: ${{ secrets.PACKAGE_CLEANUP_APP_KEY }}
|
|
116
|
+
|
|
117
|
+
- name: Delete old container image versions
|
|
118
|
+
uses: actions/delete-package-versions@v5
|
|
119
|
+
with:
|
|
120
|
+
package-name: my-image
|
|
121
|
+
package-type: container
|
|
122
|
+
min-versions-to-keep: 5
|
|
123
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
124
|
+
prevention:
|
|
125
|
+
- "Never assume packages:write grants deletion rights — it only covers publish and metadata operations."
|
|
126
|
+
- "Provision a dedicated classic PAT or GitHub App with package admin access for all cleanup/retention workflows."
|
|
127
|
+
- "Document which secrets are needed for deletion workflows so maintainers do not accidentally replace them with GITHUB_TOKEN."
|
|
128
|
+
- "Audit all uses of actions/delete-package-versions in your org to confirm each workflow uses an appropriate token."
|
|
129
|
+
- "Prefer GitHub App tokens over classic PATs for org workflows — PAT credentials are tied to a specific user account."
|
|
130
|
+
docs:
|
|
131
|
+
- url: "https://docs.github.com/en/packages/learn-github-packages/about-permissions-for-github-packages"
|
|
132
|
+
label: "GitHub Docs — About permissions for GitHub Packages (granular vs repository-scoped)"
|
|
133
|
+
- url: "https://github.com/actions/delete-package-versions"
|
|
134
|
+
label: "actions/delete-package-versions — token requirements"
|
|
135
|
+
- url: "https://docs.github.com/en/rest/packages/packages"
|
|
136
|
+
label: "GitHub REST API — Packages DELETE endpoints"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
id: runner-environment-219
|
|
2
|
+
title: 'pnpm/action-setup@v6.0.0 Always Installs pnpm v11 Regardless of Requested Version'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- pnpm
|
|
7
|
+
- action-setup
|
|
8
|
+
- pnpm-v11
|
|
9
|
+
- version-mismatch
|
|
10
|
+
- path-priority
|
|
11
|
+
- lockfile
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'ERR_PNPM_UNSUPPORTED_ENGINE.*Unsupported environment'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'Expected version:.*Got:.*11\.'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'ERR_PNPM_LOCKFILE_CONFIG_MISMATCH.*frozen installation'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
- regex: 'ERR_PNPM_BROKEN_LOCKFILE.*expected a single document'
|
|
20
|
+
flags: 'i'
|
|
21
|
+
error_messages:
|
|
22
|
+
- "ERR_PNPM_UNSUPPORTED_ENGINE Unsupported environment (bad pnpm and/or Node.js version)"
|
|
23
|
+
- "Expected version: 10\n Got: 11.0.0-beta.4-1"
|
|
24
|
+
- "ERR_PNPM_LOCKFILE_CONFIG_MISMATCH Cannot proceed with the frozen installation. The current \"overrides\" configuration doesn't match the value found in the lockfile"
|
|
25
|
+
- "ERR_PNPM_BROKEN_LOCKFILE The lockfile at \"<path>/pnpm-lock.yaml\" is broken: expected a single document in the stream, but found more"
|
|
26
|
+
root_cause: |
|
|
27
|
+
pnpm/action-setup@v6.0.0 introduced a "self-managed" bootstrap mechanism where
|
|
28
|
+
pnpm installs itself using pnpm. The action downloads a bootstrap binary (pnpm v11)
|
|
29
|
+
and uses it to self-update to the requested version. However, in v6.0.0 through
|
|
30
|
+
v6.0.7, a PATH priority bug caused the bootstrap binary to shadow the correctly
|
|
31
|
+
installed version at runtime:
|
|
32
|
+
|
|
33
|
+
addPath(path.join(pnpmHome, 'bin')) // correct binary installed here
|
|
34
|
+
addPath(pnpmHome) // bootstrap v11 binary lives here
|
|
35
|
+
|
|
36
|
+
Because addPath prepends in reverse call order, PNPM_HOME (containing the bootstrap
|
|
37
|
+
v11 binary) was always first on PATH, shadowing PNPM_HOME/bin (containing the
|
|
38
|
+
self-updated target version). Any workflow step that ran `pnpm` therefore used v11
|
|
39
|
+
regardless of the `version:` input.
|
|
40
|
+
|
|
41
|
+
Side effects of this bug:
|
|
42
|
+
1. workflows specifying `version: 10` run with pnpm v11, triggering
|
|
43
|
+
ERR_PNPM_UNSUPPORTED_ENGINE if engines.pnpm is set in package.json
|
|
44
|
+
2. pnpm v11 writes `configDependencies` blocks into pnpm-lock.yaml during any
|
|
45
|
+
install, corrupting the lockfile from pnpm v10's perspective; subsequent
|
|
46
|
+
`--frozen-lockfile` installs fail with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH
|
|
47
|
+
3. When using `package_json_file:` input, the wrong version is read because pnpm
|
|
48
|
+
v11 changed lockfile format, causing ERR_PNPM_BROKEN_LOCKFILE when the v11
|
|
49
|
+
bootstrap writes partial YAML to the lockfile before the correct version takes
|
|
50
|
+
over
|
|
51
|
+
|
|
52
|
+
The fix (addPath call order swap) was merged in PR #230 and released in v6.0.1.
|
|
53
|
+
A separate bootstrap bundle update for pnpm 11.0.0-rc.2 was released in v6.0.3.
|
|
54
|
+
Full stability for most scenarios reached v6.0.8.
|
|
55
|
+
fix: |
|
|
56
|
+
Upgrade to pnpm/action-setup@v6.0.8 or later. The PATH priority bug and
|
|
57
|
+
configDependencies lockfile corruption are fixed in v6.0.1+.
|
|
58
|
+
|
|
59
|
+
If you cannot upgrade, pin back to v5:
|
|
60
|
+
|
|
61
|
+
- uses: pnpm/action-setup@v5
|
|
62
|
+
with:
|
|
63
|
+
version: 10
|
|
64
|
+
|
|
65
|
+
If you are on v6 and seeing lockfile corruption: delete the corrupted
|
|
66
|
+
pnpm-lock.yaml and regenerate it with the correct pnpm version locally
|
|
67
|
+
before re-running CI. Do NOT run pnpm install in the corrupted-lockfile
|
|
68
|
+
state as it will write a v11 lockfile into your repo.
|
|
69
|
+
fix_code:
|
|
70
|
+
- language: yaml
|
|
71
|
+
label: 'Broken — pnpm/action-setup@v6.0.0 installs v11 regardless of version input'
|
|
72
|
+
code: |
|
|
73
|
+
- uses: pnpm/action-setup@v6 # v6.0.0: PATH bug installs v11 always
|
|
74
|
+
with:
|
|
75
|
+
version: 10 # Ignored — v11 ends up on PATH
|
|
76
|
+
- uses: actions/setup-node@v4
|
|
77
|
+
with:
|
|
78
|
+
node-version: '22'
|
|
79
|
+
cache: 'pnpm'
|
|
80
|
+
- run: pnpm install --frozen-lockfile
|
|
81
|
+
# Fails: ERR_PNPM_LOCKFILE_CONFIG_MISMATCH or ERR_PNPM_UNSUPPORTED_ENGINE
|
|
82
|
+
|
|
83
|
+
- language: yaml
|
|
84
|
+
label: 'Fixed — upgrade to v6.0.8+ (PATH priority bug resolved)'
|
|
85
|
+
code: |
|
|
86
|
+
- uses: pnpm/action-setup@v6.0.8 # v6.0.8: PATH priority fixed, stable
|
|
87
|
+
with:
|
|
88
|
+
version: 10
|
|
89
|
+
- uses: actions/setup-node@v4
|
|
90
|
+
with:
|
|
91
|
+
node-version: '22'
|
|
92
|
+
cache: 'pnpm'
|
|
93
|
+
- run: pnpm install --frozen-lockfile
|
|
94
|
+
|
|
95
|
+
- language: yaml
|
|
96
|
+
label: 'Alternative fix — pin back to v5 until v6 is stable for your project'
|
|
97
|
+
code: |
|
|
98
|
+
- uses: pnpm/action-setup@v5
|
|
99
|
+
with:
|
|
100
|
+
version: 10
|
|
101
|
+
- uses: actions/setup-node@v4
|
|
102
|
+
with:
|
|
103
|
+
node-version: '22'
|
|
104
|
+
cache: 'pnpm'
|
|
105
|
+
- run: pnpm install --frozen-lockfile
|
|
106
|
+
|
|
107
|
+
prevention:
|
|
108
|
+
- 'Pin pnpm/action-setup to a specific minor/patch version (e.g. @v6.0.8) rather than @v6 to avoid silent major-bump breakage from Dependabot.'
|
|
109
|
+
- 'After upgrading pnpm/action-setup, verify `pnpm --version` in the first step matches the requested version before running install.'
|
|
110
|
+
- 'If pnpm-lock.yaml is corrupted by this bug, delete it and regenerate locally with the correct pnpm version — do not attempt to fix by re-running CI.'
|
|
111
|
+
- 'Set `engines.pnpm` in package.json to detect unexpected version mismatches early with ERR_PNPM_UNSUPPORTED_ENGINE rather than silent wrong-behavior.'
|
|
112
|
+
docs:
|
|
113
|
+
- url: 'https://github.com/pnpm/action-setup/issues/225'
|
|
114
|
+
label: 'pnpm/action-setup#225 — v6.0.0 always installs v11 (51 reactions)'
|
|
115
|
+
- url: 'https://github.com/pnpm/action-setup/issues/226'
|
|
116
|
+
label: 'pnpm/action-setup#226 — v6.0.0 modifies pnpm-lock.yaml (lockfile corruption)'
|
|
117
|
+
- url: 'https://github.com/pnpm/action-setup/pull/230'
|
|
118
|
+
label: 'pnpm/action-setup#230 — Fix: swap addPath call order (merged)'
|
|
119
|
+
- url: 'https://github.com/pnpm/action-setup/releases'
|
|
120
|
+
label: 'pnpm/action-setup releases — v6.0.1 contains the PATH fix'
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
id: runner-environment-220
|
|
2
|
+
title: 'Node.js 20 JavaScript Actions Runtime Deprecated — Forced Migration to Node.js 24 (June 2026)'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- nodejs
|
|
7
|
+
- node20
|
|
8
|
+
- node24
|
|
9
|
+
- javascript-actions
|
|
10
|
+
- deprecation
|
|
11
|
+
- runtime
|
|
12
|
+
- actions-upgrade
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'Node\.js 20 actions are deprecated'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'running on Node\.js 20 and may not work as expected'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
- regex: 'Actions will be forced to run with Node\.js 24 by default'
|
|
19
|
+
flags: 'i'
|
|
20
|
+
- regex: 'FORCE_JAVASCRIPT_ACTIONS_TO_NODE24'
|
|
21
|
+
flags: 'i'
|
|
22
|
+
error_messages:
|
|
23
|
+
- "Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/deploy-pages@v4."
|
|
24
|
+
- "Actions will be forced to run with Node.js 24 by default starting June 2nd, 2026."
|
|
25
|
+
- "Please check if updated versions of these actions are available that support Node.js 24."
|
|
26
|
+
root_cause: |
|
|
27
|
+
GitHub Actions JavaScript action runtimes follow Node.js LTS lifecycle.
|
|
28
|
+
Node.js 20 reached end-of-life in April 2026, and GitHub deprecated it as a
|
|
29
|
+
supported JavaScript actions runtime in September 2025:
|
|
30
|
+
|
|
31
|
+
- Sept 2025: Deprecation announced. Warning annotation added to workflow runs.
|
|
32
|
+
- Feb 2026: Runner v2.322+ begins emitting "Node.js 20 actions are deprecated"
|
|
33
|
+
annotation for any action whose package.json specifies "main" with runs.using: node20.
|
|
34
|
+
- June 2, 2026: Forced migration. Actions using node20 runtime are automatically
|
|
35
|
+
upgraded to run on Node.js 24 unless ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true
|
|
36
|
+
is set.
|
|
37
|
+
|
|
38
|
+
Actions affected are those that declare `runs.using: node20` in their action.yml.
|
|
39
|
+
This is a property of the action itself, not the workflow caller. Common examples
|
|
40
|
+
that shipped with node20 and required major version bumps:
|
|
41
|
+
- actions/deploy-pages@v4 → upgrade to @v5
|
|
42
|
+
- actions/upload-pages-artifact@v3 → upgrade to @v4
|
|
43
|
+
- Third-party actions on older major versions
|
|
44
|
+
|
|
45
|
+
The warning does NOT cause workflow failures in most cases — the runner upgrades
|
|
46
|
+
the action to run under Node.js 24 automatically starting June 2, 2026. However:
|
|
47
|
+
1. Actions using APIs removed or changed between Node.js 20 and 24 (e.g., internal
|
|
48
|
+
crypto or buffer APIs) may silently produce wrong results or throw runtime errors
|
|
49
|
+
2. The warning annotation shows up in every workflow run's annotation panel,
|
|
50
|
+
cluttering the UI and masking real warnings
|
|
51
|
+
3. Some security-conscious pipelines treat any annotation as a build failure
|
|
52
|
+
via fail-on-annotations settings
|
|
53
|
+
fix: |
|
|
54
|
+
1. Identify which actions in your workflow are still on node20 runtime:
|
|
55
|
+
Look for "Node.js 20 actions are deprecated" annotations in the Workflow Annotations
|
|
56
|
+
panel (gear icon next to workflow run summary).
|
|
57
|
+
|
|
58
|
+
2. Upgrade the pinned version of the affected action to a release that uses node24:
|
|
59
|
+
- actions/deploy-pages@v4 → @v5
|
|
60
|
+
- actions/upload-pages-artifact@v3 → @v4
|
|
61
|
+
- For third-party actions: check their releases for "node24" or "Node.js 24" mentions
|
|
62
|
+
|
|
63
|
+
3. If no node24 version of a required action exists yet, set the opt-out env var
|
|
64
|
+
as a temporary measure (not recommended for production):
|
|
65
|
+
env:
|
|
66
|
+
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: 'true'
|
|
67
|
+
|
|
68
|
+
4. To opt into Node.js 24 early for a specific workflow (before June 2 deadline):
|
|
69
|
+
env:
|
|
70
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
|
|
71
|
+
fix_code:
|
|
72
|
+
- language: yaml
|
|
73
|
+
label: 'Broken — actions/deploy-pages@v4 uses node20 runtime (shows deprecation warning)'
|
|
74
|
+
code: |
|
|
75
|
+
jobs:
|
|
76
|
+
deploy:
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
steps:
|
|
79
|
+
- name: Deploy to GitHub Pages
|
|
80
|
+
uses: actions/deploy-pages@v4 # node20 runtime — triggers deprecation warning
|
|
81
|
+
|
|
82
|
+
- language: yaml
|
|
83
|
+
label: 'Fixed — upgrade to v5 which uses node24 runtime'
|
|
84
|
+
code: |
|
|
85
|
+
jobs:
|
|
86
|
+
deploy:
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
steps:
|
|
89
|
+
- name: Deploy to GitHub Pages
|
|
90
|
+
uses: actions/deploy-pages@v5 # node24 runtime — no deprecation warning
|
|
91
|
+
|
|
92
|
+
- language: yaml
|
|
93
|
+
label: 'Temporary opt-out — allow node20 actions to run (not recommended long-term)'
|
|
94
|
+
code: |
|
|
95
|
+
jobs:
|
|
96
|
+
deploy:
|
|
97
|
+
runs-on: ubuntu-latest
|
|
98
|
+
env:
|
|
99
|
+
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: 'true' # Suppresses forced upgrade
|
|
100
|
+
steps:
|
|
101
|
+
- uses: actions/deploy-pages@v4 # Still runs on node20 (insecure)
|
|
102
|
+
|
|
103
|
+
- language: yaml
|
|
104
|
+
label: 'Find all node20 actions in your workflow via actionlint annotation grep'
|
|
105
|
+
code: |
|
|
106
|
+
# Run actionlint locally or in CI to list all outdated action runtimes:
|
|
107
|
+
# actionlint .github/workflows/*.yml | grep "node20"
|
|
108
|
+
#
|
|
109
|
+
# Or use gh CLI to scan your workflow annotation after a run:
|
|
110
|
+
# gh run view <run-id> --json annotations --jq '.annotations[] | select(.message | contains("Node.js 20"))'
|
|
111
|
+
|
|
112
|
+
prevention:
|
|
113
|
+
- 'Subscribe to the GitHub Actions changelog (https://github.blog/changelog/label/github-actions/) to catch runtime deprecation announcements early.'
|
|
114
|
+
- 'Use Dependabot for GitHub Actions to get automatic PRs when new major versions of actions are released with updated runtimes.'
|
|
115
|
+
- 'Run actionlint in CI — it can detect actions using deprecated node runtime versions.'
|
|
116
|
+
- 'When pinning third-party actions to SHA hashes, periodically check if newer major versions with updated runtimes have been released.'
|
|
117
|
+
docs:
|
|
118
|
+
- url: 'https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/'
|
|
119
|
+
label: 'GitHub Changelog — Deprecation of Node.js 20 on GitHub Actions runners (Sept 2025)'
|
|
120
|
+
- url: 'https://github.com/actions/deploy-pages/issues/410'
|
|
121
|
+
label: 'actions/deploy-pages#410 — Support Node.js 24 (36 reactions)'
|
|
122
|
+
- url: 'https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#runsusing'
|
|
123
|
+
label: 'GitHub Docs — runs.using in action.yml (supported runtime versions)'
|
package/package.json
CHANGED