@htekdev/actions-debugger 1.0.8 → 1.0.10
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/upload-artifact-v4-same-name-409-conflict.yml +145 -0
- package/errors/concurrency-timing/concurrency-group-name-collision.yml +165 -0
- package/errors/known-unsolved/upload-artifact-v4-ghes-not-supported.yml +120 -0
- package/errors/permissions-auth/deploy-pages-missing-permissions.yml +144 -0
- package/errors/permissions-auth/fine-grained-pat-actions-scope-missing.yml +128 -0
- package/errors/silent-failures/upload-artifact-v4-hidden-files-excluded.yml +121 -0
- package/errors/triggers/repository-dispatch-event-type-mismatch.yml +116 -0
- package/errors/triggers/workflow-call-required-input-not-supplied.yml +115 -0
- package/errors/triggers/workflow-dispatch-required-input-api-bypass.yml +114 -0
- package/errors/yaml-syntax/vars-context-rejected-composite-actions.yml +127 -0
- package/package.json +1 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
id: caching-artifacts-012
|
|
2
|
+
title: "upload-artifact v4 Rejects Duplicate Artifact Names from Multiple Jobs (409 Conflict)"
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- upload-artifact
|
|
7
|
+
- artifact-v4
|
|
8
|
+
- matrix
|
|
9
|
+
- parallel-jobs
|
|
10
|
+
- 409
|
|
11
|
+
- immutability
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Failed to CreateArtifact.*409.*Conflict.*artifact.*already exists"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "artifact.*already exists.*workflow run|duplicate.*artifact.*name"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "Received non-retryable error.*409.*Conflict"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Failed to CreateArtifact: Received non-retryable error: Failed request: (409) Conflict: an artifact with this name already exists on the workflow run"
|
|
21
|
+
- "Error: ENOENT: no such file or directory — artifact upload failed with conflict"
|
|
22
|
+
- "Upload failed: 409 Conflict — Artifact 'build-output' already exists"
|
|
23
|
+
root_cause: |
|
|
24
|
+
**actions/upload-artifact v4** (backed by the v2 `@actions/artifact` client) makes
|
|
25
|
+
artifacts **immutable and job-scoped by default**. Once an artifact name is uploaded
|
|
26
|
+
by any job in a workflow run, that name is locked — a second job attempting to upload
|
|
27
|
+
an artifact with the same name will receive a `409 Conflict` error from the Artifact
|
|
28
|
+
API and the upload fails.
|
|
29
|
+
|
|
30
|
+
This is a **breaking behavior change from v3**, which allowed multiple jobs to
|
|
31
|
+
append to the same artifact name. v4 deliberately prevents this to ensure artifact
|
|
32
|
+
content integrity and predictable downloads.
|
|
33
|
+
|
|
34
|
+
**Common scenarios that trigger this:**
|
|
35
|
+
|
|
36
|
+
1. **Matrix jobs** — all matrix jobs use the same artifact name (e.g., `build-output`)
|
|
37
|
+
and upload in parallel; only the first to complete succeeds.
|
|
38
|
+
|
|
39
|
+
2. **Retry logic** — a job is retried (manually or via `retry-on-failure`) and the
|
|
40
|
+
original run already uploaded the artifact under the same name.
|
|
41
|
+
|
|
42
|
+
3. **Split test/build jobs** — multiple parallel jobs each generate and upload an
|
|
43
|
+
artifact with a shared generic name (e.g., `test-results`).
|
|
44
|
+
|
|
45
|
+
4. **Copy-pasted workflows** — two separate jobs have identical `upload-artifact`
|
|
46
|
+
steps with the same name.
|
|
47
|
+
|
|
48
|
+
The artifact immutability change was introduced to enable the new artifact download
|
|
49
|
+
model where artifacts are uniquely addressable and not corrupted by multiple writers.
|
|
50
|
+
fix: |
|
|
51
|
+
**Use unique artifact names per job.** The standard pattern is to embed job/matrix
|
|
52
|
+
context in the artifact name so each upload is distinct.
|
|
53
|
+
|
|
54
|
+
For matrix jobs, include the matrix value in the name. For parallel jobs, include
|
|
55
|
+
the job name. The v4 `download-artifact` action supports wildcard patterns, so all
|
|
56
|
+
job-scoped artifacts can be downloaded together in a merge step.
|
|
57
|
+
fix_code:
|
|
58
|
+
- language: yaml
|
|
59
|
+
label: "Matrix jobs — include matrix value in artifact name"
|
|
60
|
+
code: |
|
|
61
|
+
jobs:
|
|
62
|
+
build:
|
|
63
|
+
strategy:
|
|
64
|
+
matrix:
|
|
65
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
66
|
+
runs-on: ${{ matrix.os }}
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v4
|
|
69
|
+
|
|
70
|
+
- name: Build
|
|
71
|
+
run: make build
|
|
72
|
+
|
|
73
|
+
# ❌ Causes 409: all matrix jobs use same name
|
|
74
|
+
# - uses: actions/upload-artifact@v4
|
|
75
|
+
# with:
|
|
76
|
+
# name: build-output
|
|
77
|
+
# path: dist/
|
|
78
|
+
|
|
79
|
+
# ✅ Unique name per matrix dimension
|
|
80
|
+
- uses: actions/upload-artifact@v4
|
|
81
|
+
with:
|
|
82
|
+
name: build-output-${{ matrix.os }}
|
|
83
|
+
path: dist/
|
|
84
|
+
|
|
85
|
+
package:
|
|
86
|
+
needs: build
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
steps:
|
|
89
|
+
# ✅ Download all matrix artifacts with wildcard
|
|
90
|
+
- uses: actions/download-artifact@v4
|
|
91
|
+
with:
|
|
92
|
+
pattern: build-output-*
|
|
93
|
+
merge-multiple: true
|
|
94
|
+
path: dist/
|
|
95
|
+
- language: yaml
|
|
96
|
+
label: "Parallel jobs — unique names with merge step"
|
|
97
|
+
code: |
|
|
98
|
+
jobs:
|
|
99
|
+
test-unit:
|
|
100
|
+
runs-on: ubuntu-latest
|
|
101
|
+
steps:
|
|
102
|
+
- uses: actions/checkout@v4
|
|
103
|
+
- run: npm test -- --reporter=junit --output-file=test-results.xml
|
|
104
|
+
- uses: actions/upload-artifact@v4
|
|
105
|
+
with:
|
|
106
|
+
name: test-results-unit # unique name
|
|
107
|
+
path: test-results.xml
|
|
108
|
+
|
|
109
|
+
test-integration:
|
|
110
|
+
runs-on: ubuntu-latest
|
|
111
|
+
steps:
|
|
112
|
+
- uses: actions/checkout@v4
|
|
113
|
+
- run: npm run test:integration -- --output-file=test-results.xml
|
|
114
|
+
- uses: actions/upload-artifact@v4
|
|
115
|
+
with:
|
|
116
|
+
name: test-results-integration # unique name
|
|
117
|
+
path: test-results.xml
|
|
118
|
+
|
|
119
|
+
report:
|
|
120
|
+
needs: [test-unit, test-integration]
|
|
121
|
+
runs-on: ubuntu-latest
|
|
122
|
+
steps:
|
|
123
|
+
- uses: actions/download-artifact@v4
|
|
124
|
+
with:
|
|
125
|
+
pattern: test-results-*
|
|
126
|
+
merge-multiple: true
|
|
127
|
+
path: all-results/
|
|
128
|
+
prevention:
|
|
129
|
+
- "Always include a unique identifier in artifact names when multiple jobs upload
|
|
130
|
+
artifacts — use `${{ matrix.* }}`, `${{ github.job }}`, or a descriptive suffix."
|
|
131
|
+
- "Treat artifact names like immutable keys: once uploaded in a run, they cannot be
|
|
132
|
+
overwritten; design names accordingly."
|
|
133
|
+
- "Use `download-artifact@v4` with `pattern:` and `merge-multiple: true` to collect
|
|
134
|
+
artifacts from multiple jobs into a single directory in a downstream job."
|
|
135
|
+
- "If your workflow retries jobs automatically, include the attempt number
|
|
136
|
+
(`${{ github.run_attempt }}`) in the artifact name to avoid conflicts on retry."
|
|
137
|
+
docs:
|
|
138
|
+
- url: "https://github.com/actions/upload-artifact/issues/478"
|
|
139
|
+
label: "actions/upload-artifact#478 — 409 Conflict: artifact with this name already exists"
|
|
140
|
+
- url: "https://github.com/actions/upload-artifact/blob/main/RELEASES.md"
|
|
141
|
+
label: "upload-artifact v4 release notes — immutability change"
|
|
142
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/storing-and-sharing-data-from-a-workflow"
|
|
143
|
+
label: "GitHub Docs: Storing and sharing data from a workflow"
|
|
144
|
+
- url: "https://github.com/actions/download-artifact?tab=readme-ov-file#download-multiple-artifacts"
|
|
145
|
+
label: "download-artifact: Downloading multiple artifacts with patterns"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
id: concurrency-timing-009
|
|
2
|
+
title: "Concurrency Group Name Collision Cancels Unrelated Workflows"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- concurrency
|
|
7
|
+
- concurrency-group
|
|
8
|
+
- cancel-in-progress
|
|
9
|
+
- workflow-cancellation
|
|
10
|
+
- naming
|
|
11
|
+
- cross-workflow
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "concurrency.*group.*cancelled|run.*cancelled.*concurrency"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "previous run.*same concurrency group.*cancelled"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "workflow run.*cancelled.*due to.*concurrency"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "This workflow run was cancelled because a more recent run with the same concurrency group is in progress."
|
|
21
|
+
- "Workflow cancelled: superseded by a newer run in the same concurrency group."
|
|
22
|
+
- "Run #XXXX was cancelled because run #YYYY (same concurrency group 'deploy') is queued."
|
|
23
|
+
root_cause: |
|
|
24
|
+
GitHub Actions concurrency groups operate **across all workflows** that share the
|
|
25
|
+
same group name string in the same repository. If two different workflows (or two
|
|
26
|
+
jobs in different workflows) declare the same `concurrency.group` name, they compete
|
|
27
|
+
for the same queue — leading to unintended cancellations between completely unrelated
|
|
28
|
+
workflows.
|
|
29
|
+
|
|
30
|
+
**Example of the collision pattern:**
|
|
31
|
+
```yaml
|
|
32
|
+
# workflow-a.yml — CI pipeline
|
|
33
|
+
concurrency:
|
|
34
|
+
group: deploy
|
|
35
|
+
cancel-in-progress: true
|
|
36
|
+
|
|
37
|
+
# workflow-b.yml — Release pipeline (independent)
|
|
38
|
+
concurrency:
|
|
39
|
+
group: deploy # ← same name! collides with workflow-a
|
|
40
|
+
cancel-in-progress: true
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
In this example, triggering `workflow-b` cancels any in-progress run of `workflow-a`
|
|
44
|
+
and vice versa, even though they are completely independent processes.
|
|
45
|
+
|
|
46
|
+
**Common sources of collisions:**
|
|
47
|
+
- Copy-pasting a workflow file and forgetting to rename the concurrency group
|
|
48
|
+
- Using generic names like `deploy`, `build`, `ci`, `test`
|
|
49
|
+
- Multiple reusable workflows called from different callers with the same
|
|
50
|
+
`concurrency.group` in the caller
|
|
51
|
+
- Organization-wide template workflows with hardcoded group names
|
|
52
|
+
|
|
53
|
+
**Why this is a silent failure:** The cancellation log message appears in the
|
|
54
|
+
cancelled workflow run, but developers may not notice or may attribute the cancellation
|
|
55
|
+
to an unrelated cause. The triggering workflow (the one that caused the cancellation)
|
|
56
|
+
shows no indication it cancelled another workflow.
|
|
57
|
+
|
|
58
|
+
**Note:** Starting May 2026, GitHub Actions added support for **larger concurrency
|
|
59
|
+
queues** (`queue: max` option), allowing more pending runs instead of cancelling them.
|
|
60
|
+
This reduces pain but does not eliminate collisions caused by shared group names.
|
|
61
|
+
fix: |
|
|
62
|
+
Make concurrency group names unique and descriptive by including the workflow name,
|
|
63
|
+
branch/ref, and optionally the event type. Never use generic single-word names
|
|
64
|
+
like `deploy`, `ci`, or `build` as the sole concurrency group name.
|
|
65
|
+
|
|
66
|
+
A safe pattern is: `${{ github.workflow }}-${{ github.ref }}`
|
|
67
|
+
This scopes the group to the specific workflow and branch combination, preventing
|
|
68
|
+
cross-workflow and cross-branch collisions.
|
|
69
|
+
fix_code:
|
|
70
|
+
- language: yaml
|
|
71
|
+
label: "Use workflow + ref in concurrency group name"
|
|
72
|
+
code: |
|
|
73
|
+
# ✅ Safe pattern: workflow name + branch prevents cross-workflow collisions
|
|
74
|
+
name: Deploy Production
|
|
75
|
+
|
|
76
|
+
on:
|
|
77
|
+
push:
|
|
78
|
+
branches: [main]
|
|
79
|
+
|
|
80
|
+
concurrency:
|
|
81
|
+
# Unique per workflow + branch combination
|
|
82
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
83
|
+
cancel-in-progress: true
|
|
84
|
+
|
|
85
|
+
jobs:
|
|
86
|
+
deploy:
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
steps:
|
|
89
|
+
- uses: actions/checkout@v4
|
|
90
|
+
- run: ./deploy.sh
|
|
91
|
+
- language: yaml
|
|
92
|
+
label: "Per-PR concurrency — cancel superseded PR runs only"
|
|
93
|
+
code: |
|
|
94
|
+
# ✅ Scope to PR number — only cancels stale runs of the same PR
|
|
95
|
+
name: CI
|
|
96
|
+
|
|
97
|
+
on:
|
|
98
|
+
pull_request:
|
|
99
|
+
|
|
100
|
+
concurrency:
|
|
101
|
+
group: ${{ github.workflow }}-pr-${{ github.event.pull_request.number }}
|
|
102
|
+
cancel-in-progress: true
|
|
103
|
+
|
|
104
|
+
jobs:
|
|
105
|
+
test:
|
|
106
|
+
runs-on: ubuntu-latest
|
|
107
|
+
steps:
|
|
108
|
+
- uses: actions/checkout@v4
|
|
109
|
+
- run: npm test
|
|
110
|
+
- language: yaml
|
|
111
|
+
label: "Job-level concurrency — scope to specific job, not whole workflow"
|
|
112
|
+
code: |
|
|
113
|
+
name: CI + Deploy
|
|
114
|
+
|
|
115
|
+
on:
|
|
116
|
+
push:
|
|
117
|
+
branches: [main]
|
|
118
|
+
|
|
119
|
+
jobs:
|
|
120
|
+
test:
|
|
121
|
+
runs-on: ubuntu-latest
|
|
122
|
+
# No concurrency on test job — let tests always run
|
|
123
|
+
steps:
|
|
124
|
+
- uses: actions/checkout@v4
|
|
125
|
+
- run: npm test
|
|
126
|
+
|
|
127
|
+
deploy:
|
|
128
|
+
needs: test
|
|
129
|
+
runs-on: ubuntu-latest
|
|
130
|
+
# Concurrency only on deploy, scoped to this specific workflow + job
|
|
131
|
+
concurrency:
|
|
132
|
+
group: ${{ github.workflow }}-deploy-${{ github.ref }}
|
|
133
|
+
cancel-in-progress: false # queue deploys, don't cancel them
|
|
134
|
+
steps:
|
|
135
|
+
- uses: actions/checkout@v4
|
|
136
|
+
- run: ./deploy.sh
|
|
137
|
+
- language: yaml
|
|
138
|
+
label: "Larger queue option (May 2026+) — allow pending runs instead of cancelling"
|
|
139
|
+
code: |
|
|
140
|
+
# ✅ Use larger queues to queue runs instead of cancelling
|
|
141
|
+
# Requires GitHub Actions concurrency queue support (May 2026+)
|
|
142
|
+
concurrency:
|
|
143
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
144
|
+
cancel-in-progress: false # do not cancel; queue instead
|
|
145
|
+
# queue: max # allow maximum pending runs (if supported)
|
|
146
|
+
prevention:
|
|
147
|
+
- "Always include `${{ github.workflow }}` in the concurrency group name to prevent
|
|
148
|
+
cross-workflow collisions — bare names like `deploy` or `build` are collision-prone."
|
|
149
|
+
- "Include the ref (`${{ github.ref }}`) in group names so parallel PRs/branches
|
|
150
|
+
don't cancel each other's runs."
|
|
151
|
+
- "Audit all workflow files for concurrency group names when onboarding new workflows
|
|
152
|
+
into a repository — duplicates cause mysterious cancellations that are hard to trace."
|
|
153
|
+
- "Use job-level concurrency (not workflow-level) when only certain jobs (e.g., deploy)
|
|
154
|
+
need serialization — keep CI/test jobs unrestricted."
|
|
155
|
+
- "Document the concurrency group naming convention in your repository's CONTRIBUTING
|
|
156
|
+
or workflow guidelines so future contributors follow the same pattern."
|
|
157
|
+
docs:
|
|
158
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs"
|
|
159
|
+
label: "GitHub Docs: Control the concurrency of workflows and jobs"
|
|
160
|
+
- url: "https://github.blog/changelog/2026-05-07-github-actions-concurrency-groups-now-allow-larger-queues"
|
|
161
|
+
label: "GitHub Changelog: Concurrency groups now allow larger queues (May 2026)"
|
|
162
|
+
- url: "https://oneuptime.com/blog/post/2026-01-25-github-actions-concurrency-control/view"
|
|
163
|
+
label: "GitHub Actions Concurrency Control — best practices blog post"
|
|
164
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs#example-using-concurrency-to-cancel-any-in-progress-job-or-run"
|
|
165
|
+
label: "GitHub Docs: Concurrency examples — cancel in-progress"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
id: known-unsolved-010
|
|
2
|
+
title: "upload-artifact v4 and download-artifact v4 Not Supported on GitHub Enterprise Server"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- upload-artifact
|
|
7
|
+
- download-artifact
|
|
8
|
+
- artifact-v4
|
|
9
|
+
- ghes
|
|
10
|
+
- github-enterprise-server
|
|
11
|
+
- compatibility
|
|
12
|
+
- enterprise
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "@actions/artifact v2\\.0\\.0.*not.*supported.*GHES|upload-artifact@v4.*not.*supported.*GHES"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "download-artifact@v4.*not currently supported on GHES"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "requires.*newer.*version.*GHES|GHES.*does not support.*artifact.*v[234]"
|
|
19
|
+
flags: "i"
|
|
20
|
+
error_messages:
|
|
21
|
+
- "Error: @actions/artifact v2.0.0+, upload-artifact@v4+ and download-artifact@v4+ are not currently supported on GHES"
|
|
22
|
+
- "Error: upload-artifact@v4 is not supported on your current GHES version. Use v3 instead."
|
|
23
|
+
- "Failed to create artifact: The artifact service is not available on this instance"
|
|
24
|
+
root_cause: |
|
|
25
|
+
**actions/upload-artifact v4** and **actions/download-artifact v4** depend on a
|
|
26
|
+
new Artifact API (`@actions/artifact` v2+) that uses the Blob service and updated
|
|
27
|
+
artifact storage endpoints introduced in GitHub.com in late 2023. This API is
|
|
28
|
+
**not available on GitHub Enterprise Server (GHES)** instances running versions
|
|
29
|
+
prior to the minimum supported release for the new artifact service.
|
|
30
|
+
|
|
31
|
+
As of mid-2026, artifact v4 support on GHES has a version gate. GHES instances
|
|
32
|
+
that have not been upgraded to the minimum supported version will fail immediately
|
|
33
|
+
when any workflow step uses `upload-artifact@v4` or `download-artifact@v4`, with
|
|
34
|
+
a clear error message indicating the feature is unsupported.
|
|
35
|
+
|
|
36
|
+
**Key facts:**
|
|
37
|
+
- `upload-artifact@v3` and `download-artifact@v3` continue to work on all GHES versions
|
|
38
|
+
- There is no workaround that makes v4 work on unsupported GHES versions
|
|
39
|
+
- Self-hosted runners connected to GHES are subject to the same restriction as
|
|
40
|
+
GitHub-hosted runners on that instance
|
|
41
|
+
- The error is immediate (not a race condition or transient issue) — the artifact
|
|
42
|
+
service API rejects the request at connection time
|
|
43
|
+
|
|
44
|
+
**Impact:** Teams that copy workflows from GitHub.com repositories or follow
|
|
45
|
+
documentation written for GitHub.com find their enterprise CI broken after upgrading
|
|
46
|
+
actions to v4. The fix is to pin back to v3 on GHES or upgrade the GHES instance.
|
|
47
|
+
fix: |
|
|
48
|
+
**Option 1 (recommended for most teams):** Pin `upload-artifact` and
|
|
49
|
+
`download-artifact` to `@v3` on your GHES-connected workflows until your GHES
|
|
50
|
+
instance is upgraded to a version that supports the new artifact service.
|
|
51
|
+
|
|
52
|
+
**Option 2:** Upgrade your GHES instance to the minimum version that supports
|
|
53
|
+
artifact v4. Consult the GHES release notes to find the minimum supported version.
|
|
54
|
+
|
|
55
|
+
**Option 3:** Use conditional version selection based on the `github.server_url`
|
|
56
|
+
context to use v4 on GitHub.com and v3 on GHES.
|
|
57
|
+
fix_code:
|
|
58
|
+
- language: yaml
|
|
59
|
+
label: "Pin to v3 on GHES instances"
|
|
60
|
+
code: |
|
|
61
|
+
jobs:
|
|
62
|
+
build:
|
|
63
|
+
runs-on: self-hosted
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
|
|
67
|
+
- name: Build
|
|
68
|
+
run: make build
|
|
69
|
+
|
|
70
|
+
# ✅ Use v3 on GHES until your instance supports v4
|
|
71
|
+
- uses: actions/upload-artifact@v3
|
|
72
|
+
with:
|
|
73
|
+
name: build-output
|
|
74
|
+
path: dist/
|
|
75
|
+
retention-days: 7
|
|
76
|
+
|
|
77
|
+
deploy:
|
|
78
|
+
needs: build
|
|
79
|
+
runs-on: self-hosted
|
|
80
|
+
steps:
|
|
81
|
+
# ✅ Match the upload version for download
|
|
82
|
+
- uses: actions/download-artifact@v3
|
|
83
|
+
with:
|
|
84
|
+
name: build-output
|
|
85
|
+
path: dist/
|
|
86
|
+
- language: yaml
|
|
87
|
+
label: "Conditional version — v4 on GitHub.com, v3 on GHES"
|
|
88
|
+
code: |
|
|
89
|
+
jobs:
|
|
90
|
+
build:
|
|
91
|
+
runs-on: ${{ github.server_url == 'https://github.com' && 'ubuntu-latest' || 'self-hosted' }}
|
|
92
|
+
steps:
|
|
93
|
+
- uses: actions/checkout@v4
|
|
94
|
+
|
|
95
|
+
- name: Build
|
|
96
|
+
run: make build
|
|
97
|
+
|
|
98
|
+
# GitHub.com: use v4; GHES: use v3
|
|
99
|
+
- uses: ${{ github.server_url == 'https://github.com' && 'actions/upload-artifact@v4' || 'actions/upload-artifact@v3' }}
|
|
100
|
+
with:
|
|
101
|
+
name: build-output
|
|
102
|
+
path: dist/
|
|
103
|
+
prevention:
|
|
104
|
+
- "Before upgrading to upload-artifact@v4 in a repository, verify whether the
|
|
105
|
+
repository is hosted on GitHub.com or a GHES instance."
|
|
106
|
+
- "For multi-environment repositories (same workflow runs on both GitHub.com and GHES),
|
|
107
|
+
use conditional `uses:` expressions or maintain separate workflow files per environment."
|
|
108
|
+
- "Monitor GHES release notes for artifact v4 support announcements before upgrading
|
|
109
|
+
enterprise workflows."
|
|
110
|
+
- "Pin action versions explicitly in GHES-hosted workflows — avoid floating `@latest`
|
|
111
|
+
tags that may resolve to v4 on GHES before the instance supports it."
|
|
112
|
+
docs:
|
|
113
|
+
- url: "https://stackoverflow.com/questions/79267706/error-actions-artifact-v2-0-0-upload-artifactv4-and-download-artifactv4"
|
|
114
|
+
label: "Stack Overflow: upload-artifact@v4 and download-artifact@v4 not supported on GHES"
|
|
115
|
+
- url: "https://github.com/actions/upload-artifact/blob/main/RELEASES.md"
|
|
116
|
+
label: "upload-artifact v4 release notes — GHES compatibility notes"
|
|
117
|
+
- url: "https://docs.github.com/en/enterprise-server@latest/admin/release-notes"
|
|
118
|
+
label: "GitHub Enterprise Server release notes"
|
|
119
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/storing-and-sharing-data-from-a-workflow"
|
|
120
|
+
label: "GitHub Docs: Storing and sharing data from a workflow"
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
id: permissions-auth-009
|
|
2
|
+
title: "actions/deploy-pages Fails 403 — Missing pages: write or id-token: write Permissions"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- deploy-pages
|
|
7
|
+
- pages
|
|
8
|
+
- permissions
|
|
9
|
+
- id-token
|
|
10
|
+
- oidc
|
|
11
|
+
- github-token
|
|
12
|
+
- 403
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "Failed to create deployment.*status: 403.*pages: write"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "Resource not accessible by integration"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "Fetching artifact metadata failed.*Resource not accessible by integration"
|
|
19
|
+
flags: "i"
|
|
20
|
+
- regex: "Error: Error: Failed to create deployment \\(status: 403\\)"
|
|
21
|
+
flags: "i"
|
|
22
|
+
error_messages:
|
|
23
|
+
- "Error: Fetching artifact metadata failed. Is githubstatus.com reporting issues with API requests, Pages or Actions? Please re-run the deployment at a later time."
|
|
24
|
+
- "Error: HttpError: Resource not accessible by integration"
|
|
25
|
+
- "Error: Error: Failed to create deployment (status: 403) with build version abc123. Ensure GITHUB_TOKEN has permission \"pages: write\"."
|
|
26
|
+
- "Creating Pages deployment failed"
|
|
27
|
+
root_cause: |
|
|
28
|
+
`actions/deploy-pages` uses the GitHub Pages REST API, which requires an OIDC-issued
|
|
29
|
+
JWT token for authentication. This imposes three distinct permissions that must all be
|
|
30
|
+
present on the **deploy job** (not the build job):
|
|
31
|
+
|
|
32
|
+
1. `pages: write` — allows the GITHUB_TOKEN to create and update GitHub Pages deployments.
|
|
33
|
+
2. `id-token: write` — allows the runner to request an OIDC JWT from GitHub's token
|
|
34
|
+
endpoint. Without this, `deploy-pages` cannot obtain the token used to authenticate
|
|
35
|
+
the Pages API call, resulting in the misleading "Resource not accessible by integration"
|
|
36
|
+
HTTP 403 error.
|
|
37
|
+
3. `contents: read` — required to read the uploaded artifact. Often already set by
|
|
38
|
+
inheritance but must be explicit when a workflow-level `permissions:` block restricts
|
|
39
|
+
all tokens to read-only.
|
|
40
|
+
|
|
41
|
+
**Why this is confusing:**
|
|
42
|
+
- The error message says "Resource not accessible by integration" which looks like a generic
|
|
43
|
+
API permission problem, not a missing `id-token` scope.
|
|
44
|
+
- The `pages: write` permission alone is insufficient — `id-token: write` is equally
|
|
45
|
+
required and its absence produces the exact same error.
|
|
46
|
+
- Permissions set at the **workflow level** do not automatically apply to jobs that override
|
|
47
|
+
them. The deploy job must declare its own `permissions:` block.
|
|
48
|
+
- The artifact upload (via `actions/upload-pages-artifact`) happens in a SEPARATE job from
|
|
49
|
+
the deploy — permissions for upload and deploy must be set independently.
|
|
50
|
+
|
|
51
|
+
**Source:** Confirmed in `actions/deploy-pages` issues #285, #286 (Dec 2023) — over 16
|
|
52
|
+
reactions and widespread community impact.
|
|
53
|
+
fix: |
|
|
54
|
+
Add all three required permissions to the deploy job. The build job that runs
|
|
55
|
+
`upload-pages-artifact` does not need these permissions, but the deploy job does.
|
|
56
|
+
|
|
57
|
+
Also verify that GitHub Pages is enabled for the repository under
|
|
58
|
+
Settings → Pages → Source → "GitHub Actions".
|
|
59
|
+
fix_code:
|
|
60
|
+
- language: yaml
|
|
61
|
+
label: "Correct two-job Pages deployment workflow with required permissions"
|
|
62
|
+
code: |
|
|
63
|
+
name: Deploy GitHub Pages
|
|
64
|
+
|
|
65
|
+
on:
|
|
66
|
+
push:
|
|
67
|
+
branches: [main]
|
|
68
|
+
|
|
69
|
+
# Deny all permissions at workflow level; grant explicitly per job
|
|
70
|
+
permissions:
|
|
71
|
+
contents: read
|
|
72
|
+
|
|
73
|
+
jobs:
|
|
74
|
+
build:
|
|
75
|
+
runs-on: ubuntu-latest
|
|
76
|
+
steps:
|
|
77
|
+
- uses: actions/checkout@v4
|
|
78
|
+
|
|
79
|
+
- name: Build site
|
|
80
|
+
run: npm run build # outputs to ./dist
|
|
81
|
+
|
|
82
|
+
- name: Upload Pages artifact
|
|
83
|
+
uses: actions/upload-pages-artifact@v3
|
|
84
|
+
with:
|
|
85
|
+
path: ./dist
|
|
86
|
+
|
|
87
|
+
deploy:
|
|
88
|
+
needs: build
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
permissions:
|
|
91
|
+
pages: write # ← Required: create/update Pages deployment
|
|
92
|
+
id-token: write # ← Required: obtain OIDC JWT for Pages API auth
|
|
93
|
+
contents: read # ← Required: read the uploaded artifact
|
|
94
|
+
environment:
|
|
95
|
+
name: github-pages
|
|
96
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
97
|
+
steps:
|
|
98
|
+
- name: Deploy to GitHub Pages
|
|
99
|
+
id: deployment
|
|
100
|
+
uses: actions/deploy-pages@v4
|
|
101
|
+
- language: yaml
|
|
102
|
+
label: "Single-job variant — all permissions in one job"
|
|
103
|
+
code: |
|
|
104
|
+
name: Deploy Pages (single job)
|
|
105
|
+
|
|
106
|
+
on:
|
|
107
|
+
push:
|
|
108
|
+
branches: [main]
|
|
109
|
+
|
|
110
|
+
jobs:
|
|
111
|
+
deploy:
|
|
112
|
+
runs-on: ubuntu-latest
|
|
113
|
+
permissions:
|
|
114
|
+
pages: write
|
|
115
|
+
id-token: write
|
|
116
|
+
contents: read
|
|
117
|
+
environment:
|
|
118
|
+
name: github-pages
|
|
119
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
120
|
+
steps:
|
|
121
|
+
- uses: actions/checkout@v4
|
|
122
|
+
- name: Build
|
|
123
|
+
run: npm run build
|
|
124
|
+
- uses: actions/upload-pages-artifact@v3
|
|
125
|
+
with:
|
|
126
|
+
path: ./dist
|
|
127
|
+
- id: deployment
|
|
128
|
+
uses: actions/deploy-pages@v4
|
|
129
|
+
prevention:
|
|
130
|
+
- "Always set `pages: write`, `id-token: write`, and `contents: read` on the deploy job — not the build job and not only at the workflow level."
|
|
131
|
+
- "Confirm GitHub Pages is enabled under repository Settings → Pages → Source → 'GitHub Actions' before running the workflow."
|
|
132
|
+
- "When using a workflow-level `permissions:` block that restricts defaults, remember job-level permissions do not inherit write scopes — they must be re-declared."
|
|
133
|
+
- "The `environment: github-pages` block is required for the Pages API to create a deployment — do not omit it."
|
|
134
|
+
docs:
|
|
135
|
+
- url: "https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow"
|
|
136
|
+
label: "GitHub Docs: Publishing GitHub Pages with Actions"
|
|
137
|
+
- url: "https://github.com/actions/deploy-pages"
|
|
138
|
+
label: "actions/deploy-pages — official action repository"
|
|
139
|
+
- url: "https://github.com/actions/deploy-pages/issues/285"
|
|
140
|
+
label: "actions/deploy-pages #285: Failing to fetch artifact metadata since 4.0.0"
|
|
141
|
+
- url: "https://github.com/actions/deploy-pages/issues/286"
|
|
142
|
+
label: "actions/deploy-pages #286: Bump to V4 broke the deploy step"
|
|
143
|
+
- url: "https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect"
|
|
144
|
+
label: "GitHub Docs: Security hardening with OpenID Connect"
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
id: permissions-auth-010
|
|
2
|
+
title: "Fine-Grained PAT Missing Actions Permission — Workflow Dispatch and API Calls Fail 403"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- fine-grained-pat
|
|
7
|
+
- pat
|
|
8
|
+
- personal-access-token
|
|
9
|
+
- actions
|
|
10
|
+
- workflow-dispatch
|
|
11
|
+
- 403
|
|
12
|
+
- api
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "HTTP 403.*fine.?grained.*token"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "Resource not accessible by integration"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "Must have admin rights to Repository\\."
|
|
19
|
+
flags: "i"
|
|
20
|
+
- regex: "403.*workflow.*dispatch"
|
|
21
|
+
flags: "i"
|
|
22
|
+
error_messages:
|
|
23
|
+
- "HTTP 403: {\"message\":\"Resource not accessible by integration\",\"documentation_url\":\"https://docs.github.com/rest/actions/workflows\"}"
|
|
24
|
+
- "Error: error making HTTP request: 403 Resource not accessible by integration"
|
|
25
|
+
- "gh: Could not run workflow: HTTP 422 Unprocessable Entity"
|
|
26
|
+
- "RequestError [HttpError]: Resource not accessible by integration"
|
|
27
|
+
root_cause: |
|
|
28
|
+
GitHub fine-grained personal access tokens (introduced 2022, GA 2023) do NOT include
|
|
29
|
+
the "Actions" permission scope by default when created. This is different from classic
|
|
30
|
+
PATs where the `repo` scope implicitly granted access to Actions APIs.
|
|
31
|
+
|
|
32
|
+
When a fine-grained PAT is used to:
|
|
33
|
+
- Trigger workflows via REST API (`POST /repos/{owner}/{repo}/actions/workflows/{id}/dispatches`)
|
|
34
|
+
- List or cancel workflow runs
|
|
35
|
+
- Download workflow run logs
|
|
36
|
+
- Use `gh workflow run` / `gh run list` with the PAT as `GH_TOKEN`
|
|
37
|
+
|
|
38
|
+
...the API returns HTTP 403 "Resource not accessible by integration" because the
|
|
39
|
+
fine-grained token lacks the explicit `Actions: read` or `Actions: write` permission.
|
|
40
|
+
|
|
41
|
+
**Why this affects more users over time:**
|
|
42
|
+
- GitHub is progressively deprecating classic PATs in favor of fine-grained PATs.
|
|
43
|
+
- Organization policies can require fine-grained PATs only, blocking classic PAT usage.
|
|
44
|
+
- Automation pipelines that worked with classic `repo`-scoped PATs silently break when
|
|
45
|
+
migrated to fine-grained PATs if the Actions permission is not explicitly added.
|
|
46
|
+
- The error message "Resource not accessible by integration" is the same as the one
|
|
47
|
+
shown for missing `GITHUB_TOKEN` scopes, making diagnosis harder.
|
|
48
|
+
|
|
49
|
+
**Required permissions on the fine-grained PAT by use case:**
|
|
50
|
+
| Use case | Required permission |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Trigger workflow (`workflow_dispatch`) | Actions: write |
|
|
53
|
+
| Cancel / re-run workflow run | Actions: write |
|
|
54
|
+
| List workflow runs / jobs | Actions: read |
|
|
55
|
+
| Download workflow run logs | Actions: read |
|
|
56
|
+
| Read workflow YAML definitions | Actions: read |
|
|
57
|
+
fix: |
|
|
58
|
+
Regenerate or edit the fine-grained PAT and add the **Actions** permission with the
|
|
59
|
+
appropriate access level:
|
|
60
|
+
|
|
61
|
+
- **Read-only** — for listing runs, reading logs, checking run status.
|
|
62
|
+
- **Read and write** — for dispatching workflows, cancelling runs, re-triggering jobs.
|
|
63
|
+
|
|
64
|
+
Navigate to: GitHub → Settings → Developer Settings → Personal access tokens →
|
|
65
|
+
Fine-grained tokens → (select token) → Permissions → Repository permissions →
|
|
66
|
+
Actions → set to "Read and write".
|
|
67
|
+
|
|
68
|
+
If the repository is in an organization, also ensure the organization has not restricted
|
|
69
|
+
fine-grained PAT usage to specific resources that exclude this repository.
|
|
70
|
+
fix_code:
|
|
71
|
+
- language: yaml
|
|
72
|
+
label: "Using a fine-grained PAT with Actions permission to trigger a workflow"
|
|
73
|
+
code: |
|
|
74
|
+
# Store the fine-grained PAT as a repository secret: DEPLOY_PAT
|
|
75
|
+
# The PAT must have: Actions: write, Contents: read (minimum)
|
|
76
|
+
|
|
77
|
+
name: Trigger Deployment
|
|
78
|
+
|
|
79
|
+
on:
|
|
80
|
+
workflow_dispatch:
|
|
81
|
+
|
|
82
|
+
jobs:
|
|
83
|
+
trigger:
|
|
84
|
+
runs-on: ubuntu-latest
|
|
85
|
+
steps:
|
|
86
|
+
- name: Dispatch downstream workflow
|
|
87
|
+
run: |
|
|
88
|
+
gh workflow run deploy.yml \
|
|
89
|
+
-f environment=production \
|
|
90
|
+
--repo org/downstream-repo
|
|
91
|
+
env:
|
|
92
|
+
GH_TOKEN: ${{ secrets.DEPLOY_PAT }} # Fine-grained PAT with Actions: write
|
|
93
|
+
- language: yaml
|
|
94
|
+
label: "Required PAT permissions checklist for common Actions API uses"
|
|
95
|
+
code: |
|
|
96
|
+
# Fine-grained PAT permission requirements:
|
|
97
|
+
#
|
|
98
|
+
# Trigger workflow dispatch → Actions: Write
|
|
99
|
+
# Cancel a workflow run → Actions: Write
|
|
100
|
+
# Re-run failed jobs → Actions: Write
|
|
101
|
+
# List workflow runs → Actions: Read
|
|
102
|
+
# Download run logs → Actions: Read
|
|
103
|
+
# Get workflow definition → Actions: Read
|
|
104
|
+
# Approve environment deployment → Actions: Write + Environments: Write
|
|
105
|
+
#
|
|
106
|
+
# Classic PAT 'repo' scope covers all of the above implicitly.
|
|
107
|
+
# Fine-grained PATs require explicit opt-in per permission category.
|
|
108
|
+
|
|
109
|
+
# To check if your PAT has the right permissions:
|
|
110
|
+
- name: Verify PAT has Actions permission
|
|
111
|
+
run: |
|
|
112
|
+
gh api /repos/${{ github.repository }}/actions/workflows \
|
|
113
|
+
--jq '.total_count'
|
|
114
|
+
env:
|
|
115
|
+
GH_TOKEN: ${{ secrets.DEPLOY_PAT }} # Returns 403 if Actions: read is missing
|
|
116
|
+
prevention:
|
|
117
|
+
- "When migrating from classic PATs to fine-grained PATs, explicitly audit which permission categories the classic `repo` scope was implicitly covering and add each one."
|
|
118
|
+
- "Add `Actions: read` at minimum to any fine-grained PAT that will be used in GitHub Actions pipelines, even for read-only operations."
|
|
119
|
+
- "Store fine-grained PATs as org-level or repository secrets and add a comment documenting which permissions the PAT requires."
|
|
120
|
+
- "Use a dedicated machine account (bot user) for automation PATs so permissions can be audited independently of individual developer accounts."
|
|
121
|
+
- "If an organization policy requires fine-grained PATs, update all automation onboarding docs to include the Actions permission explicitly."
|
|
122
|
+
docs:
|
|
123
|
+
- url: "https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token"
|
|
124
|
+
label: "GitHub Docs: Creating a fine-grained personal access token"
|
|
125
|
+
- url: "https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event"
|
|
126
|
+
label: "REST API: Create a workflow dispatch event"
|
|
127
|
+
- url: "https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization"
|
|
128
|
+
label: "GitHub Docs: PAT policy for organizations"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
id: silent-failures-012
|
|
2
|
+
title: "upload-artifact v4.4.0 Silently Excludes Hidden Files and Directories"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- upload-artifact
|
|
7
|
+
- artifact-v4
|
|
8
|
+
- hidden-files
|
|
9
|
+
- dotfiles
|
|
10
|
+
- path-patterns
|
|
11
|
+
- silent
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "upload.*artifact.*no files.*found|artifact.*upload.*0.*bytes"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Artifact.*successfully uploaded.*0 files|uploaded 0 item"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "include-hidden-files"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Warning: No files were found with the provided path: .env.dist"
|
|
21
|
+
- "Artifact upload: 0 items, 0 bytes"
|
|
22
|
+
- "With the provided path, there will be 0 files uploaded"
|
|
23
|
+
- "Uploaded artifact 'dotfiles' (0 bytes)"
|
|
24
|
+
root_cause: |
|
|
25
|
+
Starting with **actions/upload-artifact v4.4.0** (released February 2026), the action
|
|
26
|
+
excludes files and directories whose names begin with `.` (dot/hidden files) **by
|
|
27
|
+
default**. This is a **silent behavior change** — the upload action still reports
|
|
28
|
+
"success" but skips all dotfiles, resulting in an artifact that is missing expected
|
|
29
|
+
content without any error or warning in most cases.
|
|
30
|
+
|
|
31
|
+
**Files and directories affected:**
|
|
32
|
+
- `.env`, `.env.dist`, `.env.example`, `.env.test`
|
|
33
|
+
- `.gitignore`, `.gitattributes`, `.eslintrc`, `.prettierrc`
|
|
34
|
+
- `.docker/`, `.github/`, `.cache/`, `.npm/`
|
|
35
|
+
- Any file or directory with a name starting with `.`
|
|
36
|
+
|
|
37
|
+
**Why this is a silent failure:** The action reports "Artifact uploaded successfully"
|
|
38
|
+
even when all matched files happen to be hidden files and thus are excluded. In cases
|
|
39
|
+
where the path glob matches only hidden files, the artifact is created with 0 bytes
|
|
40
|
+
and no error is thrown. Downstream jobs that download and use the artifact will
|
|
41
|
+
receive an empty directory or encounter missing file errors, far from the upload step.
|
|
42
|
+
|
|
43
|
+
**Historical context:** This change was introduced for security consistency —
|
|
44
|
+
dotfiles often contain secrets (`.env`), credentials, or sensitive configuration that
|
|
45
|
+
developers might accidentally expose through artifacts. The default exclusion prevents
|
|
46
|
+
accidental secret leakage via artifacts.
|
|
47
|
+
fix: |
|
|
48
|
+
Set `include-hidden-files: true` on the `upload-artifact` step to restore the
|
|
49
|
+
previous behavior and include dotfiles in the artifact.
|
|
50
|
+
|
|
51
|
+
**Security note:** Before enabling `include-hidden-files: true`, review whether
|
|
52
|
+
the files being uploaded contain secrets. Consider using GitHub secrets or environment
|
|
53
|
+
variables for sensitive values rather than uploading `.env` files as artifacts.
|
|
54
|
+
|
|
55
|
+
Alternatively, rename configuration files to not start with `.` if they don't
|
|
56
|
+
contain sensitive values (e.g., `env.example` instead of `.env.example`).
|
|
57
|
+
fix_code:
|
|
58
|
+
- language: yaml
|
|
59
|
+
label: "Enable hidden file inclusion with include-hidden-files"
|
|
60
|
+
code: |
|
|
61
|
+
jobs:
|
|
62
|
+
build:
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
|
|
67
|
+
- name: Build
|
|
68
|
+
run: make build
|
|
69
|
+
|
|
70
|
+
# ❌ v4.4.0+: .env.dist, .config/, etc. are silently excluded
|
|
71
|
+
# - uses: actions/upload-artifact@v4
|
|
72
|
+
# with:
|
|
73
|
+
# name: build-output
|
|
74
|
+
# path: dist/
|
|
75
|
+
|
|
76
|
+
# ✅ Explicitly include hidden files when needed
|
|
77
|
+
- uses: actions/upload-artifact@v4
|
|
78
|
+
with:
|
|
79
|
+
name: build-output
|
|
80
|
+
path: dist/
|
|
81
|
+
include-hidden-files: true # restores pre-v4.4.0 behavior
|
|
82
|
+
- language: yaml
|
|
83
|
+
label: "Targeted upload — avoid hidden files where possible"
|
|
84
|
+
code: |
|
|
85
|
+
jobs:
|
|
86
|
+
build:
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
steps:
|
|
89
|
+
- uses: actions/checkout@v4
|
|
90
|
+
|
|
91
|
+
- name: Build
|
|
92
|
+
run: make build
|
|
93
|
+
|
|
94
|
+
# ✅ Better: upload specific file types, not entire directories
|
|
95
|
+
# This avoids accidentally including or excluding hidden files
|
|
96
|
+
- uses: actions/upload-artifact@v4
|
|
97
|
+
with:
|
|
98
|
+
name: build-artifacts
|
|
99
|
+
path: |
|
|
100
|
+
dist/**/*.js
|
|
101
|
+
dist/**/*.css
|
|
102
|
+
dist/**/*.html
|
|
103
|
+
env.example # renamed from .env.example to avoid hiding
|
|
104
|
+
prevention:
|
|
105
|
+
- "Audit workflows that upload artifacts containing dotfiles (`.env.*`, `.config`,
|
|
106
|
+
`.cache/`) — add `include-hidden-files: true` explicitly if those files are needed."
|
|
107
|
+
- "Check artifact sizes after upgrading to v4.4.0+: a 0-byte or unexpectedly small
|
|
108
|
+
artifact often indicates hidden files were silently excluded."
|
|
109
|
+
- "For environment template files that should be shared as artifacts, rename them
|
|
110
|
+
to not start with `.` (e.g., `env.template` instead of `.env.template`)."
|
|
111
|
+
- "Never upload actual `.env` files with real secrets as artifacts — use GitHub
|
|
112
|
+
secrets or OIDC-based secret retrieval instead."
|
|
113
|
+
docs:
|
|
114
|
+
- url: "https://stackoverflow.com/questions/78941839/github-actions-upload-artifactv4-4-0-path-patterns-not-working-after-upgrade"
|
|
115
|
+
label: "Stack Overflow: upload-artifact v4.4.0 path patterns not working (hidden files)"
|
|
116
|
+
- url: "https://github.blog/changelog/2026-02-26-github-actions-now-supports-uploading-and-downloading-non-zipped-artifacts"
|
|
117
|
+
label: "GitHub Changelog: artifact upload/download improvements (Feb 2026)"
|
|
118
|
+
- url: "https://github.com/actions/upload-artifact/blob/main/RELEASES.md"
|
|
119
|
+
label: "upload-artifact v4 release notes"
|
|
120
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/storing-and-sharing-data-from-a-workflow"
|
|
121
|
+
label: "GitHub Docs: Storing and sharing data from a workflow"
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
id: triggers-010
|
|
2
|
+
title: "repository_dispatch Event Type Not in types: Filter — Workflow Silently Skipped"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- repository_dispatch
|
|
7
|
+
- event-type
|
|
8
|
+
- types-filter
|
|
9
|
+
- triggers
|
|
10
|
+
- api
|
|
11
|
+
- silent
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "repository_dispatch"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "types:\\s*\\[.*\\]"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "client_payload"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "No runs found for workflow"
|
|
21
|
+
- "Workflow not triggered by repository_dispatch event"
|
|
22
|
+
root_cause: |
|
|
23
|
+
A `repository_dispatch` workflow only fires when the dispatched `event_type` value
|
|
24
|
+
matches one of the values in the `types:` filter. If the event type sent via the API
|
|
25
|
+
does not match, GitHub silently drops it — no error, no notification, the workflow simply
|
|
26
|
+
never starts.
|
|
27
|
+
|
|
28
|
+
**Common failure scenarios:**
|
|
29
|
+
|
|
30
|
+
1. **Typo or case mismatch**: `event_type: "Deploy_Staging"` dispatched but workflow
|
|
31
|
+
has `types: [deploy_staging]` (underscore vs case sensitivity). Event types are
|
|
32
|
+
case-sensitive.
|
|
33
|
+
|
|
34
|
+
2. **Omitting the types: filter entirely — workflow fires for ALL events** (opposite
|
|
35
|
+
problem): Without a `types:` filter, the workflow runs for every `repository_dispatch`
|
|
36
|
+
event, including internal automation events not intended to trigger it.
|
|
37
|
+
|
|
38
|
+
3. **API payload uses wrong field name**: The REST API requires the `event_type` field
|
|
39
|
+
under the JSON body. Some clients accidentally nest it differently or omit it entirely,
|
|
40
|
+
causing the dispatch to use the default empty event type which matches nothing.
|
|
41
|
+
|
|
42
|
+
4. **Cross-workflow automation chain**: Workflow A dispatches event type `build-complete`.
|
|
43
|
+
Workflow B listens for `build_complete` (underscore). The chain silently breaks with no
|
|
44
|
+
logs in either workflow.
|
|
45
|
+
|
|
46
|
+
**API payload shape:**
|
|
47
|
+
```
|
|
48
|
+
POST /repos/{owner}/{repo}/dispatches
|
|
49
|
+
{
|
|
50
|
+
"event_type": "deploy_staging", ← Must exactly match types: filter
|
|
51
|
+
"client_payload": { ... }
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
fix: |
|
|
55
|
+
Verify that the `event_type` string in the API POST body matches exactly (case-sensitive)
|
|
56
|
+
one of the values declared in the workflow's `types:` filter.
|
|
57
|
+
|
|
58
|
+
To debug: add a catch-all workflow with no `types:` filter (matches all repository_dispatch
|
|
59
|
+
events) to confirm the event is arriving at all, then compare the `github.event.action`
|
|
60
|
+
value to what your workflow expects.
|
|
61
|
+
fix_code:
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: "Workflow with correctly declared repository_dispatch types filter"
|
|
64
|
+
code: |
|
|
65
|
+
on:
|
|
66
|
+
repository_dispatch:
|
|
67
|
+
types:
|
|
68
|
+
- deploy_staging # ← Must match event_type exactly (case-sensitive)
|
|
69
|
+
- deploy_production
|
|
70
|
+
- run_smoke_tests
|
|
71
|
+
|
|
72
|
+
jobs:
|
|
73
|
+
deploy:
|
|
74
|
+
runs-on: ubuntu-latest
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@v4
|
|
77
|
+
- name: Show dispatched event
|
|
78
|
+
run: |
|
|
79
|
+
echo "Event: ${{ github.event.action }}"
|
|
80
|
+
echo "Payload: ${{ toJSON(github.event.client_payload) }}"
|
|
81
|
+
- language: yaml
|
|
82
|
+
label: "Debugging workflow — catch ALL repository_dispatch events"
|
|
83
|
+
code: |
|
|
84
|
+
# Temporarily add this workflow to diagnose missing dispatches
|
|
85
|
+
on:
|
|
86
|
+
repository_dispatch: # No types: filter = receives everything
|
|
87
|
+
|
|
88
|
+
jobs:
|
|
89
|
+
debug:
|
|
90
|
+
runs-on: ubuntu-latest
|
|
91
|
+
steps:
|
|
92
|
+
- name: Log event details
|
|
93
|
+
run: |
|
|
94
|
+
echo "event_type / action: ${{ github.event.action }}"
|
|
95
|
+
echo "client_payload: ${{ toJSON(github.event.client_payload) }}"
|
|
96
|
+
- language: yaml
|
|
97
|
+
label: "Dispatching via REST API — correct payload structure"
|
|
98
|
+
code: |
|
|
99
|
+
# Using gh api to dispatch correctly
|
|
100
|
+
gh api \
|
|
101
|
+
--method POST \
|
|
102
|
+
-H "Accept: application/vnd.github+json" \
|
|
103
|
+
/repos/ORG/REPO/dispatches \
|
|
104
|
+
-f event_type="deploy_staging" \ # ← Matches types: [deploy_staging]
|
|
105
|
+
-F client_payload='{"version":"v1.2.3","env":"staging"}'
|
|
106
|
+
prevention:
|
|
107
|
+
- "Treat `event_type` values as constants — define them in a shared location (e.g., a constants file or workflow comment) used by both the dispatcher and the listener."
|
|
108
|
+
- "Add an `echo` step that logs `github.event.action` at the start of every repository_dispatch workflow to confirm the correct event type was received."
|
|
109
|
+
- "Use a catch-all debug workflow (no `types:` filter) when setting up a new dispatch chain for the first time."
|
|
110
|
+
- "Avoid changing event type strings in active automation chains — add new types instead of renaming existing ones."
|
|
111
|
+
- "Event types are case-sensitive — stick to snake_case by convention to avoid case mismatch bugs."
|
|
112
|
+
docs:
|
|
113
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#repository_dispatch"
|
|
114
|
+
label: "GitHub Actions: repository_dispatch event"
|
|
115
|
+
- url: "https://docs.github.com/en/rest/repos/repos#create-a-repository-dispatch-event"
|
|
116
|
+
label: "REST API: Create a repository dispatch event"
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
id: triggers-009
|
|
2
|
+
title: "Reusable Workflow Required Input Not Supplied — workflow_call Fails at Runtime"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- workflow_call
|
|
7
|
+
- reusable-workflow
|
|
8
|
+
- inputs
|
|
9
|
+
- required
|
|
10
|
+
- validation
|
|
11
|
+
- caller
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Input required and not supplied:\\s*\\w+"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Required input '\\w+' not provided"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "Error: Input required and not supplied"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Error: Input required and not supplied: deploy_env"
|
|
21
|
+
- "Input required and not supplied: version"
|
|
22
|
+
- "Required input 'environment' not provided by caller"
|
|
23
|
+
root_cause: |
|
|
24
|
+
Unlike `workflow_dispatch` (which treats `required: true` as UI-only), the GitHub Actions
|
|
25
|
+
runner DOES validate required inputs for `on.workflow_call` reusable workflows. If a caller
|
|
26
|
+
workflow invokes a reusable workflow without passing a declared required input, the runner
|
|
27
|
+
emits `Error: Input required and not supplied: {input_name}` and fails the job immediately.
|
|
28
|
+
|
|
29
|
+
**Common scenarios:**
|
|
30
|
+
1. **Adding a required input to an existing reusable workflow** without updating all callers.
|
|
31
|
+
Callers that worked before immediately break because the new required input is missing.
|
|
32
|
+
|
|
33
|
+
2. **Calling a reusable workflow from a matrix job** where one matrix variation doesn't
|
|
34
|
+
apply the needed input conditionally.
|
|
35
|
+
|
|
36
|
+
3. **Caller passes the input only under certain conditions** (via `if:`) — when the condition
|
|
37
|
+
is false, the input is omitted entirely rather than being passed as an empty string.
|
|
38
|
+
|
|
39
|
+
4. **Typo in the input name** — caller passes `deploy-env` but the reusable workflow expects
|
|
40
|
+
`deploy_env` (hyphens vs underscores). The declared input is not satisfied.
|
|
41
|
+
|
|
42
|
+
**Note:** The error fires at job evaluation time, not step execution time, so the entire job
|
|
43
|
+
is skipped rather than reaching the failing step.
|
|
44
|
+
fix: |
|
|
45
|
+
Ensure every caller workflow passes all required inputs declared in the reusable workflow's
|
|
46
|
+
`on.workflow_call.inputs` block.
|
|
47
|
+
|
|
48
|
+
When adding a new required input to a reusable workflow, update all callers before merging.
|
|
49
|
+
Use `required: false` with a default value if backward compatibility must be preserved.
|
|
50
|
+
|
|
51
|
+
If the input is only sometimes needed, declare it as `required: false` and check it
|
|
52
|
+
at runtime inside the reusable workflow using an early-exit validation step.
|
|
53
|
+
fix_code:
|
|
54
|
+
- language: yaml
|
|
55
|
+
label: "Reusable workflow with required input declaration"
|
|
56
|
+
code: |
|
|
57
|
+
# .github/workflows/deploy-reusable.yml
|
|
58
|
+
on:
|
|
59
|
+
workflow_call:
|
|
60
|
+
inputs:
|
|
61
|
+
environment:
|
|
62
|
+
description: "Target environment"
|
|
63
|
+
required: true # Runner enforces this for all callers
|
|
64
|
+
type: string
|
|
65
|
+
version:
|
|
66
|
+
description: "Version tag"
|
|
67
|
+
required: false
|
|
68
|
+
default: "latest"
|
|
69
|
+
type: string
|
|
70
|
+
|
|
71
|
+
jobs:
|
|
72
|
+
deploy:
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
steps:
|
|
75
|
+
- uses: actions/checkout@v4
|
|
76
|
+
- run: ./deploy.sh "${{ inputs.environment }}" "${{ inputs.version }}"
|
|
77
|
+
- language: yaml
|
|
78
|
+
label: "Caller workflow — always pass all required inputs"
|
|
79
|
+
code: |
|
|
80
|
+
# .github/workflows/release.yml
|
|
81
|
+
on:
|
|
82
|
+
push:
|
|
83
|
+
tags: ["v*"]
|
|
84
|
+
|
|
85
|
+
jobs:
|
|
86
|
+
deploy:
|
|
87
|
+
uses: ./.github/workflows/deploy-reusable.yml
|
|
88
|
+
with:
|
|
89
|
+
environment: production # ✅ Required input provided
|
|
90
|
+
version: ${{ github.ref_name }}
|
|
91
|
+
- language: yaml
|
|
92
|
+
label: "Backward-compatible: change required to optional with default"
|
|
93
|
+
code: |
|
|
94
|
+
# When adding new inputs to existing reusable workflows, use required: false
|
|
95
|
+
# with a default to avoid breaking existing callers.
|
|
96
|
+
on:
|
|
97
|
+
workflow_call:
|
|
98
|
+
inputs:
|
|
99
|
+
environment:
|
|
100
|
+
required: true
|
|
101
|
+
type: string
|
|
102
|
+
notify_slack: # New input — safe to add with required: false
|
|
103
|
+
required: false
|
|
104
|
+
default: "false"
|
|
105
|
+
type: string
|
|
106
|
+
prevention:
|
|
107
|
+
- "When adding a new `required: true` input to a reusable workflow, search all callers with `grep -r 'uses:.*deploy-reusable'` and update them simultaneously."
|
|
108
|
+
- "Prefer `required: false` with a sensible default when backward compatibility matters — validate the value inside the reusable workflow if needed."
|
|
109
|
+
- "Use consistent naming conventions (all underscores or all hyphens) for input names to avoid typo-related mismatches between callers and the called workflow."
|
|
110
|
+
- "Test reusable workflow changes in a draft PR that also updates all callers."
|
|
111
|
+
docs:
|
|
112
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows#using-inputs-and-secrets-in-a-reusable-workflow"
|
|
113
|
+
label: "GitHub Docs: Inputs in reusable workflows"
|
|
114
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_call"
|
|
115
|
+
label: "GitHub Actions: workflow_call event"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
id: triggers-008
|
|
2
|
+
title: "workflow_dispatch required: true Silently Bypassed via REST API"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow_dispatch
|
|
7
|
+
- inputs
|
|
8
|
+
- required
|
|
9
|
+
- rest-api
|
|
10
|
+
- gh-cli
|
|
11
|
+
- validation
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "workflow_dispatch.*inputs.*required.*true"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "inputs\\.\\w+.*==.*''"
|
|
16
|
+
flags: "i"
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Input required and not supplied: {input_name}"
|
|
19
|
+
root_cause: |
|
|
20
|
+
The `required: true` flag on `workflow_dispatch` inputs is enforced **only in the GitHub
|
|
21
|
+
UI** — it renders the field as mandatory in the "Run workflow" dialog. When a workflow
|
|
22
|
+
is triggered via the REST API (`POST /repos/{owner}/{repo}/actions/workflows/{id}/dispatches`)
|
|
23
|
+
or `gh workflow run`, the `required` flag is NOT validated server-side. The workflow is
|
|
24
|
+
dispatched and runs with the input set to an empty string `""` rather than the declared
|
|
25
|
+
default or an error.
|
|
26
|
+
|
|
27
|
+
This creates a subtle silent failure: developers assume `required: true` prevents invalid
|
|
28
|
+
automation, but any API caller can trigger the workflow with no inputs at all. Steps that
|
|
29
|
+
depend on the input receive an empty string without any indication something is wrong.
|
|
30
|
+
|
|
31
|
+
**Contrast with `workflow_call` (reusable workflows):** Required inputs on `on.workflow_call`
|
|
32
|
+
ARE validated by the runner — a missing required input produces `Error: Input required and
|
|
33
|
+
not supplied: {input_name}` and the job fails immediately. This inconsistency between the
|
|
34
|
+
two dispatch types frequently surprises developers.
|
|
35
|
+
|
|
36
|
+
**GitHub API behavior (documented):** The REST API returns HTTP 204 (success) even when
|
|
37
|
+
required inputs are omitted. This is by design — GitHub treats `required: true` as a UI
|
|
38
|
+
hint only.
|
|
39
|
+
fix: |
|
|
40
|
+
Treat `required: true` as UI-only and add explicit runtime validation at the top of your
|
|
41
|
+
workflow or job for any input that is truly required.
|
|
42
|
+
|
|
43
|
+
Use an `if:` guard or a validation step that fails the workflow immediately with a clear
|
|
44
|
+
error message when a required input is empty:
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
- name: Validate required inputs
|
|
48
|
+
run: |
|
|
49
|
+
if [ -z "${{ inputs.environment }}" ]; then
|
|
50
|
+
echo "::error::Input 'environment' is required but was not provided."
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
For `gh workflow run` dispatch, newer versions of the CLI (v2.40+) will prompt for
|
|
56
|
+
required inputs interactively, but non-interactive (CI) invocations must pass `-f key=value`
|
|
57
|
+
explicitly.
|
|
58
|
+
fix_code:
|
|
59
|
+
- language: yaml
|
|
60
|
+
label: "Validate required inputs at runtime with an early-exit step"
|
|
61
|
+
code: |
|
|
62
|
+
on:
|
|
63
|
+
workflow_dispatch:
|
|
64
|
+
inputs:
|
|
65
|
+
environment:
|
|
66
|
+
description: "Target environment (required)"
|
|
67
|
+
required: true
|
|
68
|
+
type: choice
|
|
69
|
+
options: [staging, production]
|
|
70
|
+
version:
|
|
71
|
+
description: "Semantic version tag to deploy (required)"
|
|
72
|
+
required: true
|
|
73
|
+
type: string
|
|
74
|
+
|
|
75
|
+
jobs:
|
|
76
|
+
deploy:
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
steps:
|
|
79
|
+
# Guard against API bypasses — required: true is UI-only
|
|
80
|
+
- name: Validate required inputs
|
|
81
|
+
run: |
|
|
82
|
+
MISSING=""
|
|
83
|
+
[ -z "${{ inputs.environment }}" ] && MISSING="$MISSING environment"
|
|
84
|
+
[ -z "${{ inputs.version }}" ] && MISSING="$MISSING version"
|
|
85
|
+
if [ -n "$MISSING" ]; then
|
|
86
|
+
echo "::error::Missing required inputs:$MISSING"
|
|
87
|
+
exit 1
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
- uses: actions/checkout@v4
|
|
91
|
+
- name: Deploy
|
|
92
|
+
run: ./deploy.sh "${{ inputs.environment }}" "${{ inputs.version }}"
|
|
93
|
+
- language: yaml
|
|
94
|
+
label: "Triggering with gh workflow run — always pass required inputs explicitly"
|
|
95
|
+
code: |
|
|
96
|
+
# ✅ Correct: pass all required inputs
|
|
97
|
+
gh workflow run deploy.yml \
|
|
98
|
+
-f environment=staging \
|
|
99
|
+
-f version=v1.2.3
|
|
100
|
+
|
|
101
|
+
# ❌ Wrong: omits required inputs — workflow still runs, inputs are empty strings
|
|
102
|
+
gh workflow run deploy.yml
|
|
103
|
+
prevention:
|
|
104
|
+
- "Never rely on `required: true` alone for server-side enforcement — add a runtime validation step."
|
|
105
|
+
- "Document in your workflow that inputs must be passed explicitly when triggering via API or CI pipelines."
|
|
106
|
+
- "For security-critical workflows (deploy, release), use a job-level `if:` condition to block the entire job when an input is empty."
|
|
107
|
+
- "Consider reusable workflow (`workflow_call`) if you need server-enforced required inputs."
|
|
108
|
+
docs:
|
|
109
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_dispatch"
|
|
110
|
+
label: "GitHub Actions: workflow_dispatch event"
|
|
111
|
+
- url: "https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event"
|
|
112
|
+
label: "REST API: Create a workflow dispatch event"
|
|
113
|
+
- url: "https://cli.github.com/manual/gh_workflow_run"
|
|
114
|
+
label: "gh workflow run — GitHub CLI manual"
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
id: yaml-syntax-016
|
|
2
|
+
title: "vars Context Rejected Inside Composite Action Expressions"
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- composite-actions
|
|
7
|
+
- vars-context
|
|
8
|
+
- expression
|
|
9
|
+
- runner-validation
|
|
10
|
+
- organization-variables
|
|
11
|
+
- repository-variables
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Unrecognized named-value.*'?vars'?"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Unexpected value.*vars\\.\\w+"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "inputs\\.\\w+.*\\|\\|.*vars\\.\\w+.*not.*recognized"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Unrecognized named-value: 'vars'. Located at position X within expression: inputs.region || vars.AWS_REGION || 'us-east-1'"
|
|
21
|
+
- "Error: Unrecognized named-value: 'vars'"
|
|
22
|
+
- "Invalid workflow file: .github/actions/my-action/action.yml (Line N, Col M): Unrecognized named-value: 'vars'"
|
|
23
|
+
root_cause: |
|
|
24
|
+
The `vars` context (organization/repository/environment variables) is **not available
|
|
25
|
+
inside composite action definitions** (`action.yml`). Runner validation enforces
|
|
26
|
+
context availability rules per the element type, and composite actions are not
|
|
27
|
+
permitted to reference `vars.*` in their step expressions.
|
|
28
|
+
|
|
29
|
+
This was not always enforced — older runner versions allowed some cross-context
|
|
30
|
+
references in composite actions, but a runner validation tightening (tracked in
|
|
31
|
+
actions/runner#4311) made this a hard error. Workflows that worked before started
|
|
32
|
+
failing after runner updates were rolled out to GitHub-hosted and self-hosted runners.
|
|
33
|
+
|
|
34
|
+
**Why the restriction exists:** Composite actions are designed to be reusable across
|
|
35
|
+
repositories and organizations. The `vars` context is scoped to the calling repository
|
|
36
|
+
or organization — a composite action cannot safely resolve `vars.*` at definition time
|
|
37
|
+
because it would create an implicit dependency on the caller's variable set without
|
|
38
|
+
explicit declaration as an `input`.
|
|
39
|
+
|
|
40
|
+
**Affected expression positions:**
|
|
41
|
+
- `with:` value fields in composite action steps
|
|
42
|
+
- `run:` shell commands using `${{ vars.* }}` syntax
|
|
43
|
+
- `if:` conditions on composite action steps
|
|
44
|
+
- Default values for composite action inputs that fall back to `vars.*`
|
|
45
|
+
fix: |
|
|
46
|
+
Remove `vars.*` references from composite action definitions. Instead, require
|
|
47
|
+
callers to pass variables explicitly as action `inputs`, and reference those inputs
|
|
48
|
+
inside the composite action.
|
|
49
|
+
|
|
50
|
+
**Pattern:**
|
|
51
|
+
1. Add an explicit `input` to the composite action for any value currently read from
|
|
52
|
+
`vars.*`
|
|
53
|
+
2. Give it a meaningful name and a default value (empty string or a sensible default)
|
|
54
|
+
3. In the calling workflow, pass the variable value as `with: my-input: ${{ vars.MY_VAR }}`
|
|
55
|
+
4. Inside the composite action, replace `${{ vars.MY_VAR }}` with `${{ inputs.my-input }}`
|
|
56
|
+
fix_code:
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: "action.yml — Replace vars.* with an explicit input"
|
|
59
|
+
code: |
|
|
60
|
+
# ❌ BROKEN: vars context not available in composite actions
|
|
61
|
+
# action.yml
|
|
62
|
+
inputs:
|
|
63
|
+
region:
|
|
64
|
+
description: "AWS region"
|
|
65
|
+
required: false
|
|
66
|
+
default: "" # cannot use: ${{ vars.AWS_REGION }}
|
|
67
|
+
|
|
68
|
+
runs:
|
|
69
|
+
using: composite
|
|
70
|
+
steps:
|
|
71
|
+
- name: Deploy
|
|
72
|
+
shell: bash
|
|
73
|
+
run: |
|
|
74
|
+
# ❌ This will fail:
|
|
75
|
+
# REGION="${{ inputs.region || vars.AWS_REGION || 'us-east-1' }}"
|
|
76
|
+
|
|
77
|
+
# ✅ Use only inputs:
|
|
78
|
+
REGION="${{ inputs.region || 'us-east-1' }}"
|
|
79
|
+
echo "Deploying to $REGION"
|
|
80
|
+
- language: yaml
|
|
81
|
+
label: "caller workflow — Pass vars.* as an explicit input"
|
|
82
|
+
code: |
|
|
83
|
+
# ✅ Calling workflow passes vars.* to the composite action explicitly
|
|
84
|
+
jobs:
|
|
85
|
+
deploy:
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
steps:
|
|
88
|
+
- uses: ./.github/actions/deploy
|
|
89
|
+
with:
|
|
90
|
+
region: ${{ vars.AWS_REGION }} # resolved in caller, not inside composite
|
|
91
|
+
- language: yaml
|
|
92
|
+
label: "action.yml — Full corrected composite action"
|
|
93
|
+
code: |
|
|
94
|
+
# ✅ CORRECT: composite action with explicit input, no vars.* reference
|
|
95
|
+
name: Deploy to AWS
|
|
96
|
+
description: Deploy application to AWS
|
|
97
|
+
|
|
98
|
+
inputs:
|
|
99
|
+
region:
|
|
100
|
+
description: "AWS region (pass vars.AWS_REGION from caller)"
|
|
101
|
+
required: false
|
|
102
|
+
default: "us-east-1"
|
|
103
|
+
|
|
104
|
+
runs:
|
|
105
|
+
using: composite
|
|
106
|
+
steps:
|
|
107
|
+
- name: Deploy
|
|
108
|
+
shell: bash
|
|
109
|
+
run: echo "Deploying to ${{ inputs.region }}"
|
|
110
|
+
prevention:
|
|
111
|
+
- "Never reference `vars.*`, `secrets.*`, or `env.*` directly inside composite action
|
|
112
|
+
step expressions — these contexts are not available at composite action evaluation time."
|
|
113
|
+
- "Treat composite actions like reusable library functions: all runtime values must
|
|
114
|
+
come in through declared `inputs`."
|
|
115
|
+
- "In the calling workflow, resolve `vars.*` or `secrets.*` at the `with:` level and
|
|
116
|
+
pass resulting values as string inputs."
|
|
117
|
+
- "Run `act` locally or validate with `actionlint` to catch context availability
|
|
118
|
+
errors before pushing to GitHub."
|
|
119
|
+
docs:
|
|
120
|
+
- url: "https://github.com/actions/runner/issues/4311"
|
|
121
|
+
label: "actions/runner#4311 — vars context rejected in composite actions"
|
|
122
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#context-availability"
|
|
123
|
+
label: "GitHub Docs: Context availability by element type"
|
|
124
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-composite-action"
|
|
125
|
+
label: "GitHub Docs: Creating a composite action"
|
|
126
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/variables#using-the-vars-context-to-access-configuration-variable-values"
|
|
127
|
+
label: "GitHub Docs: Using the vars context"
|
package/package.json
CHANGED