@htekdev/actions-debugger 1.0.41 → 1.0.43
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-duplicate-name-hard-error.yml +114 -0
- package/errors/permissions-auth/job-level-permissions-replaces-workflow-level.yml +123 -0
- package/errors/runner-environment/runner-workspace-vs-github-workspace-parent-directory.yml +100 -0
- package/errors/silent-failures/actions-runner-debug-wrong-secret-name.yml +90 -0
- package/errors/silent-failures/composite-action-outputs-not-propagated.yml +99 -0
- package/errors/silent-failures/github-env-vars-not-shared-across-jobs.yml +96 -0
- package/errors/silent-failures/matrix-exclude-value-mismatch-silently-ignored.yml +96 -0
- package/errors/triggers/workflow-run-name-mismatch-silent-never-triggers.yml +96 -0
- package/package.json +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
id: caching-artifacts-033
|
|
2
|
+
title: 'upload-artifact@v4 requires unique artifact names per run — duplicate name is a hard error'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- upload-artifact
|
|
7
|
+
- v4
|
|
8
|
+
- duplicate-name
|
|
9
|
+
- breaking-change
|
|
10
|
+
- matrix
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'An artifact with this name already exists on the workflow run'
|
|
13
|
+
flags: 'i'
|
|
14
|
+
- regex: 'Failed to CreateArtifact.*already exists'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
error_messages:
|
|
17
|
+
- 'An artifact with this name already exists on the workflow run'
|
|
18
|
+
- 'Failed to CreateArtifact: Artifact already exists'
|
|
19
|
+
root_cause: |
|
|
20
|
+
actions/upload-artifact@v4 changed artifact name uniqueness enforcement from v3:
|
|
21
|
+
|
|
22
|
+
v3 behavior: uploading with a duplicate name silently merged the new files
|
|
23
|
+
into the existing artifact, potentially overwriting same-named files.
|
|
24
|
+
|
|
25
|
+
v4 behavior: uploading with a name that already exists in the same workflow
|
|
26
|
+
run is a hard error. The step fails immediately with:
|
|
27
|
+
"An artifact with this name already exists on the workflow run"
|
|
28
|
+
|
|
29
|
+
Common triggers:
|
|
30
|
+
1. Matrix jobs all writing to a fixed artifact name (e.g., name: build-output)
|
|
31
|
+
without including a matrix dimension in the name.
|
|
32
|
+
2. Multiple upload-artifact steps in the same job with the same name: value.
|
|
33
|
+
3. Reusable workflows invoked multiple times in one run using identical names.
|
|
34
|
+
4. Two upload steps with no name: parameter — both default to "artifact".
|
|
35
|
+
|
|
36
|
+
Workflows that relied on v3's merge behavior and were silently uploading
|
|
37
|
+
duplicate names will fail immediately after upgrading to v4.
|
|
38
|
+
fix: |
|
|
39
|
+
Include a unique identifier in the artifact name for each upload step.
|
|
40
|
+
For matrix jobs, include a matrix dimension value. For independent parallel
|
|
41
|
+
jobs, include the job name or a specific run-scoped identifier.
|
|
42
|
+
|
|
43
|
+
If you need to consolidate artifacts from multiple sources, have each job
|
|
44
|
+
upload with a unique name, then use a final consolidation job that downloads
|
|
45
|
+
all artifacts and re-uploads a merged set.
|
|
46
|
+
fix_code:
|
|
47
|
+
- language: yaml
|
|
48
|
+
label: 'Matrix jobs — include matrix dimension in artifact name'
|
|
49
|
+
code: |
|
|
50
|
+
strategy:
|
|
51
|
+
matrix:
|
|
52
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
53
|
+
|
|
54
|
+
steps:
|
|
55
|
+
- name: Build
|
|
56
|
+
run: make build
|
|
57
|
+
|
|
58
|
+
- uses: actions/upload-artifact@v4
|
|
59
|
+
with:
|
|
60
|
+
name: build-output-${{ matrix.os }} # Unique per matrix cell
|
|
61
|
+
path: dist/
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: 'Multiple upload steps — use distinct explicit names'
|
|
64
|
+
code: |
|
|
65
|
+
steps:
|
|
66
|
+
- uses: actions/upload-artifact@v4
|
|
67
|
+
with:
|
|
68
|
+
name: test-results # Distinct name
|
|
69
|
+
path: test-output/
|
|
70
|
+
|
|
71
|
+
- uses: actions/upload-artifact@v4
|
|
72
|
+
with:
|
|
73
|
+
name: coverage-report # Different name — no conflict
|
|
74
|
+
path: coverage/
|
|
75
|
+
- language: yaml
|
|
76
|
+
label: 'Merge artifacts across matrix jobs in a downstream job'
|
|
77
|
+
code: |
|
|
78
|
+
jobs:
|
|
79
|
+
build:
|
|
80
|
+
strategy:
|
|
81
|
+
matrix:
|
|
82
|
+
os: [ubuntu-latest, windows-latest]
|
|
83
|
+
steps:
|
|
84
|
+
- uses: actions/upload-artifact@v4
|
|
85
|
+
with:
|
|
86
|
+
name: dist-${{ matrix.os }}
|
|
87
|
+
path: dist/
|
|
88
|
+
|
|
89
|
+
package:
|
|
90
|
+
needs: build
|
|
91
|
+
runs-on: ubuntu-latest
|
|
92
|
+
steps:
|
|
93
|
+
- uses: actions/download-artifact@v4
|
|
94
|
+
with:
|
|
95
|
+
pattern: dist-*
|
|
96
|
+
merge-multiple: true
|
|
97
|
+
path: all-dist/
|
|
98
|
+
|
|
99
|
+
- uses: actions/upload-artifact@v4
|
|
100
|
+
with:
|
|
101
|
+
name: final-package
|
|
102
|
+
path: all-dist/
|
|
103
|
+
prevention:
|
|
104
|
+
- 'Always specify an explicit name: for each upload-artifact step rather than relying on the default "artifact"'
|
|
105
|
+
- 'For matrix jobs, include at least one matrix variable in the artifact name'
|
|
106
|
+
- 'Audit workflows being migrated from v3 to v4 for any step that omits name: or uses the same name twice'
|
|
107
|
+
- 'Two steps with no name: both default to "artifact" and will always conflict in v4'
|
|
108
|
+
docs:
|
|
109
|
+
- url: 'https://github.com/actions/upload-artifact/blob/main/docs/MIGRATION.md'
|
|
110
|
+
label: 'upload-artifact v3 to v4 migration guide'
|
|
111
|
+
- url: 'https://github.com/actions/upload-artifact'
|
|
112
|
+
label: 'actions/upload-artifact repository'
|
|
113
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/storing-workflow-data-as-artifacts'
|
|
114
|
+
label: 'GitHub Docs — Storing workflow data as artifacts'
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
id: permissions-auth-034
|
|
2
|
+
title: 'Job-level permissions: block replaces workflow-level permissions — undeclared scopes are silently removed'
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- permissions
|
|
7
|
+
- GITHUB_TOKEN
|
|
8
|
+
- job-level
|
|
9
|
+
- workflow-level
|
|
10
|
+
- 403
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'Resource not accessible by integration'
|
|
13
|
+
flags: 'i'
|
|
14
|
+
- regex: 'HttpError.*403'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
error_messages:
|
|
17
|
+
- 'Resource not accessible by integration'
|
|
18
|
+
- 'HttpError: Resource not accessible by integration'
|
|
19
|
+
- '403 Forbidden'
|
|
20
|
+
root_cause: |
|
|
21
|
+
The permissions: block in GitHub Actions is a REPLACEMENT at the scope where
|
|
22
|
+
it is applied — it does NOT inherit or merge with permissions defined at a
|
|
23
|
+
parent scope.
|
|
24
|
+
|
|
25
|
+
Example: if a workflow defines top-level permissions:
|
|
26
|
+
permissions:
|
|
27
|
+
contents: write
|
|
28
|
+
issues: write
|
|
29
|
+
pull-requests: write
|
|
30
|
+
|
|
31
|
+
And a job within that workflow also defines permissions:
|
|
32
|
+
permissions:
|
|
33
|
+
deployments: write
|
|
34
|
+
|
|
35
|
+
Then that job's GITHUB_TOKEN has ONLY deployments: write. The
|
|
36
|
+
contents: write, issues: write, and pull-requests: write scopes are
|
|
37
|
+
completely dropped for that job.
|
|
38
|
+
|
|
39
|
+
Developers expect job-level permissions to be ADDITIVE (adding new
|
|
40
|
+
permissions on top of the workflow-level set). The actual behavior is the
|
|
41
|
+
opposite: job-level permissions creates an entirely new permission set for
|
|
42
|
+
that job, discarding all workflow-level grants not re-declared.
|
|
43
|
+
|
|
44
|
+
The failure is silent — no warning is emitted about the permission reduction.
|
|
45
|
+
Steps produce 403 or "Resource not accessible by integration" only when
|
|
46
|
+
they actually attempt to use the silently-removed permission.
|
|
47
|
+
fix: |
|
|
48
|
+
When using a job-level permissions: block, re-declare ALL permissions
|
|
49
|
+
that the job needs — not just the new ones you want to add.
|
|
50
|
+
|
|
51
|
+
Alternatively, if all jobs in the workflow need the same permission set,
|
|
52
|
+
set permissions only at the workflow level and omit job-level overrides.
|
|
53
|
+
|
|
54
|
+
Best practice: prefer job-level permissions over workflow-level for the
|
|
55
|
+
principle of least privilege — each job explicitly declares only what it
|
|
56
|
+
needs, with no inheritance confusion.
|
|
57
|
+
fix_code:
|
|
58
|
+
- language: yaml
|
|
59
|
+
label: 'Wrong: job-level block silently drops workflow-level grants'
|
|
60
|
+
code: |
|
|
61
|
+
# Workflow level
|
|
62
|
+
permissions:
|
|
63
|
+
contents: write
|
|
64
|
+
issues: write
|
|
65
|
+
|
|
66
|
+
jobs:
|
|
67
|
+
deploy:
|
|
68
|
+
permissions:
|
|
69
|
+
deployments: write # This REPLACES the above — contents and issues are now GONE
|
|
70
|
+
steps:
|
|
71
|
+
- name: Comment on issue
|
|
72
|
+
uses: actions/github-script@v7
|
|
73
|
+
with:
|
|
74
|
+
script: |
|
|
75
|
+
# This will 403 — issues: write was silently removed
|
|
76
|
+
await github.rest.issues.createComment({ issue_number: 1, body: 'deployed' })
|
|
77
|
+
- language: yaml
|
|
78
|
+
label: 'Correct: re-declare all needed permissions at job level'
|
|
79
|
+
code: |
|
|
80
|
+
# No workflow-level permissions block needed when using job-level
|
|
81
|
+
|
|
82
|
+
jobs:
|
|
83
|
+
deploy:
|
|
84
|
+
permissions:
|
|
85
|
+
contents: write # Re-declared (was at workflow level)
|
|
86
|
+
issues: write # Re-declared (was at workflow level)
|
|
87
|
+
deployments: write # New permission added
|
|
88
|
+
steps:
|
|
89
|
+
- name: Comment on issue
|
|
90
|
+
uses: actions/github-script@v7
|
|
91
|
+
with:
|
|
92
|
+
script: |
|
|
93
|
+
await github.rest.issues.createComment({ issue_number: 1, body: 'deployed' })
|
|
94
|
+
- language: yaml
|
|
95
|
+
label: 'Minimal job permissions — each job declares only what it needs'
|
|
96
|
+
code: |
|
|
97
|
+
# No workflow-level permissions block — all scoped to jobs
|
|
98
|
+
|
|
99
|
+
jobs:
|
|
100
|
+
test:
|
|
101
|
+
permissions:
|
|
102
|
+
checks: write
|
|
103
|
+
contents: read
|
|
104
|
+
steps:
|
|
105
|
+
- uses: actions/checkout@v4
|
|
106
|
+
|
|
107
|
+
deploy:
|
|
108
|
+
permissions:
|
|
109
|
+
contents: read
|
|
110
|
+
deployments: write
|
|
111
|
+
id-token: write
|
|
112
|
+
steps:
|
|
113
|
+
- run: echo "deploying"
|
|
114
|
+
prevention:
|
|
115
|
+
- 'When adding a job-level permissions block, start by listing every permission the job needs from scratch'
|
|
116
|
+
- 'Do not assume any permissions flow down from the workflow level when a job block is present'
|
|
117
|
+
- 'Prefer defining all permissions at the job level (not workflow level) to make the permission set explicit'
|
|
118
|
+
- 'The GitHub Docs explicitly state: permissions at the job level override the workflow-level setting'
|
|
119
|
+
docs:
|
|
120
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token'
|
|
121
|
+
label: 'GitHub Docs — Controlling permissions for GITHUB_TOKEN'
|
|
122
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idpermissions'
|
|
123
|
+
label: 'GitHub Docs — jobs.<job_id>.permissions syntax'
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
id: runner-environment-098
|
|
2
|
+
title: "runner.workspace is the parent of the repo root — not the checked-out repo"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- runner.workspace
|
|
7
|
+
- github.workspace
|
|
8
|
+
- path
|
|
9
|
+
- working-directory
|
|
10
|
+
- context
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'runner\.workspace'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: '\$\{\{\s*runner\.workspace\s*\}\}'
|
|
15
|
+
flags: i
|
|
16
|
+
error_messages:
|
|
17
|
+
- "No such file or directory"
|
|
18
|
+
- "Error: Path does not exist"
|
|
19
|
+
- "ENOENT: no such file or directory"
|
|
20
|
+
root_cause: |
|
|
21
|
+
Two similarly-named contexts point to DIFFERENT directories on GitHub-hosted
|
|
22
|
+
runners:
|
|
23
|
+
|
|
24
|
+
github.workspace = /home/runner/work/REPO/REPO ← the checked-out repo root
|
|
25
|
+
runner.workspace = /home/runner/work/REPO ← one level ABOVE the repo root
|
|
26
|
+
|
|
27
|
+
runner.workspace is the "runner work directory" that CONTAINS the repo clone
|
|
28
|
+
folder, not the repo itself. The official documentation describes this distinction,
|
|
29
|
+
but the names are confusingly similar and many developers assume runner.workspace
|
|
30
|
+
is equivalent to "where my code is" — which is github.workspace.
|
|
31
|
+
|
|
32
|
+
Consequences:
|
|
33
|
+
- working-directory: ${{ runner.workspace }} silently points one level above
|
|
34
|
+
the repo, causing file-not-found errors on relative paths inside the repo.
|
|
35
|
+
- Path-building expressions like runner.workspace + '/src' resolve to a
|
|
36
|
+
path that doesn't exist.
|
|
37
|
+
- The error surfaces as "No such file or directory" or a missing file, not as
|
|
38
|
+
a context resolution failure, making the root cause hard to identify.
|
|
39
|
+
|
|
40
|
+
On some self-hosted runner configurations both paths may resolve to the same
|
|
41
|
+
directory, masking the bug until the workflow runs on a GitHub-hosted runner.
|
|
42
|
+
|
|
43
|
+
Source: GitHub Actions contexts documentation; GitHub Community/15327;
|
|
44
|
+
Stack Overflow path confusion questions with 100K+ combined views.
|
|
45
|
+
fix: |
|
|
46
|
+
Replace runner.workspace with github.workspace (or the $GITHUB_WORKSPACE
|
|
47
|
+
environment variable) when the intent is to reference the checked-out repo root.
|
|
48
|
+
|
|
49
|
+
Use runner.workspace only when intentionally targeting the parent directory
|
|
50
|
+
that contains the repo clone — for example, creating a sibling directory for
|
|
51
|
+
build artifacts that should live outside the repo tree.
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "Use github.workspace for the repo root"
|
|
55
|
+
code: |
|
|
56
|
+
jobs:
|
|
57
|
+
build:
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v4
|
|
61
|
+
|
|
62
|
+
# ❌ WRONG — runner.workspace is one level ABOVE the repo
|
|
63
|
+
# - name: Wrong path
|
|
64
|
+
# working-directory: ${{ runner.workspace }}
|
|
65
|
+
|
|
66
|
+
# ✅ CORRECT — github.workspace is the repo root
|
|
67
|
+
- name: List repo files
|
|
68
|
+
run: ls ${{ github.workspace }}
|
|
69
|
+
|
|
70
|
+
# ✅ ALSO CORRECT — $GITHUB_WORKSPACE env var is identical
|
|
71
|
+
- name: Build from repo root
|
|
72
|
+
working-directory: ${{ github.workspace }}
|
|
73
|
+
run: ls .
|
|
74
|
+
|
|
75
|
+
# ✅ Intentional use of runner.workspace — sibling dir outside repo
|
|
76
|
+
- name: Create output dir alongside repo
|
|
77
|
+
run: mkdir ${{ runner.workspace }}/build-output
|
|
78
|
+
- language: yaml
|
|
79
|
+
label: "Directory layout reference"
|
|
80
|
+
code: |
|
|
81
|
+
# GitHub-hosted runner path layout:
|
|
82
|
+
#
|
|
83
|
+
# /home/runner/work/
|
|
84
|
+
# └── my-repo/ ← runner.workspace (${{ runner.workspace }})
|
|
85
|
+
# └── my-repo/ ← github.workspace (${{ github.workspace }})
|
|
86
|
+
# ├── src/
|
|
87
|
+
# ├── package.json
|
|
88
|
+
# └── .github/
|
|
89
|
+
#
|
|
90
|
+
# $GITHUB_WORKSPACE == ${{ github.workspace }} (same value, two access methods)
|
|
91
|
+
prevention:
|
|
92
|
+
- "Always use github.workspace (or $GITHUB_WORKSPACE) to reference the checked-out repo root"
|
|
93
|
+
- "Reserve runner.workspace for intentional parent-directory operations such as sibling build directories"
|
|
94
|
+
- "When debugging path issues, print both: echo $GITHUB_WORKSPACE and echo ${{ runner.workspace }}"
|
|
95
|
+
- "Self-hosted runner paths may differ from GitHub-hosted runners — use named contexts rather than hardcoded absolute paths"
|
|
96
|
+
docs:
|
|
97
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#github-context"
|
|
98
|
+
label: "GitHub Actions — github context (workspace)"
|
|
99
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#runner-context"
|
|
100
|
+
label: "GitHub Actions — runner context (workspace)"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
id: silent-failures-045
|
|
2
|
+
title: "ACTIONS_RUNNER_DEBUG / ACTIONS_STEP_DEBUG typo — debug logging silently disabled"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- debug
|
|
7
|
+
- ACTIONS_RUNNER_DEBUG
|
|
8
|
+
- ACTIONS_STEP_DEBUG
|
|
9
|
+
- secrets
|
|
10
|
+
- diagnostic
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'RUNNER_DEBUG'
|
|
13
|
+
flags: ''
|
|
14
|
+
- regex: 'ACTIONS_DEBUG'
|
|
15
|
+
flags: i
|
|
16
|
+
error_messages: []
|
|
17
|
+
root_cause: |
|
|
18
|
+
GitHub Actions debug logging is controlled by two specific repository secrets
|
|
19
|
+
(or repository variables since October 2023):
|
|
20
|
+
|
|
21
|
+
ACTIONS_RUNNER_DEBUG = "true" — runner diagnostic logs (runner.diag.log,
|
|
22
|
+
Worker_*.log files attached to the run as a downloadable zip)
|
|
23
|
+
ACTIONS_STEP_DEBUG = "true" — verbose step-level debug output shown
|
|
24
|
+
inline in each step log section
|
|
25
|
+
|
|
26
|
+
When developers set secrets with incorrect names such as RUNNER_DEBUG, DEBUG,
|
|
27
|
+
ACTIONS_DEBUG, or ENABLE_DEBUG, GitHub silently ignores them. The workflow
|
|
28
|
+
runs normally with no indication that debug logging was never activated.
|
|
29
|
+
The developer sees only standard log output and assumes there is nothing more
|
|
30
|
+
to inspect.
|
|
31
|
+
|
|
32
|
+
A secondary failure mode: setting these as workflow-level env: variables has
|
|
33
|
+
no effect — debug logging requires them as repository secrets or repository
|
|
34
|
+
variables, not env: entries.
|
|
35
|
+
|
|
36
|
+
Source: GitHub Docs — Enabling debug logging; GitHub Community recurring
|
|
37
|
+
reports of debug logs not appearing despite secrets being set.
|
|
38
|
+
fix: |
|
|
39
|
+
Set repository secrets or repository variables with the EXACT names:
|
|
40
|
+
ACTIONS_RUNNER_DEBUG (value: true)
|
|
41
|
+
ACTIONS_STEP_DEBUG (value: true)
|
|
42
|
+
|
|
43
|
+
Path: Repository Settings → Secrets and variables → Actions → New repository secret.
|
|
44
|
+
|
|
45
|
+
These can also be set at the organization level to apply across all repos.
|
|
46
|
+
|
|
47
|
+
Runner diagnostic logs from ACTIONS_RUNNER_DEBUG are available as a
|
|
48
|
+
downloadable zip file attached to the workflow run — not shown inline.
|
|
49
|
+
Step debug logs from ACTIONS_STEP_DEBUG appear inline in each step as
|
|
50
|
+
collapsed "##[debug]" lines.
|
|
51
|
+
fix_code:
|
|
52
|
+
- language: yaml
|
|
53
|
+
label: "workflow_dispatch input to enable debug for a single run without changing secrets"
|
|
54
|
+
code: |
|
|
55
|
+
on:
|
|
56
|
+
workflow_dispatch:
|
|
57
|
+
inputs:
|
|
58
|
+
debug_enabled:
|
|
59
|
+
type: boolean
|
|
60
|
+
description: 'Enable step debug logging for this run'
|
|
61
|
+
default: false
|
|
62
|
+
|
|
63
|
+
jobs:
|
|
64
|
+
build:
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
steps:
|
|
67
|
+
- name: Enable step debug mode
|
|
68
|
+
if: ${{ inputs.debug_enabled }}
|
|
69
|
+
run: echo "ACTIONS_STEP_DEBUG=true" >> $GITHUB_ENV
|
|
70
|
+
|
|
71
|
+
- name: Build step (verbose when debug enabled)
|
|
72
|
+
run: echo "Build running"
|
|
73
|
+
- language: yaml
|
|
74
|
+
label: "Check which debug secrets are active"
|
|
75
|
+
code: |
|
|
76
|
+
- name: Show debug context
|
|
77
|
+
run: |
|
|
78
|
+
echo "Runner debug: $ACTIONS_RUNNER_DEBUG"
|
|
79
|
+
echo "Step debug: $ACTIONS_STEP_DEBUG"
|
|
80
|
+
# If these print empty string, the secrets are not set or named incorrectly
|
|
81
|
+
prevention:
|
|
82
|
+
- "Use exactly 'ACTIONS_RUNNER_DEBUG' and 'ACTIONS_STEP_DEBUG' as the secret names — no other names work"
|
|
83
|
+
- "Set these as repository secrets or repository variables, not as env: in workflow YAML"
|
|
84
|
+
- "ACTIONS_RUNNER_DEBUG logs are in a separate zip download — check the run's uploaded artifacts section"
|
|
85
|
+
- "Both secrets can be enabled simultaneously; they control independent output streams"
|
|
86
|
+
docs:
|
|
87
|
+
- url: "https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/troubleshooting-workflows/enabling-debug-logging"
|
|
88
|
+
label: "GitHub Docs — Enabling debug logging"
|
|
89
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables"
|
|
90
|
+
label: "GitHub Docs — Storing information in variables"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
id: silent-failures-048
|
|
2
|
+
title: 'Composite action outputs not propagated — outputs: declaration required in action.yml'
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- composite
|
|
7
|
+
- outputs
|
|
8
|
+
- action.yml
|
|
9
|
+
- step-outputs
|
|
10
|
+
- empty-string
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'steps\.\w+\.outputs\.'
|
|
13
|
+
flags: 'i'
|
|
14
|
+
error_messages: []
|
|
15
|
+
root_cause: |
|
|
16
|
+
In composite actions, step outputs from internal steps are NOT automatically
|
|
17
|
+
visible to the action's callers. The composite action's action.yml must
|
|
18
|
+
declare an outputs: block that explicitly maps each output name to a value
|
|
19
|
+
expression referencing an internal step's output:
|
|
20
|
+
|
|
21
|
+
outputs:
|
|
22
|
+
my-result:
|
|
23
|
+
description: "The result"
|
|
24
|
+
value: ${{ steps.build.outputs.result }}
|
|
25
|
+
|
|
26
|
+
Without this declaration, ${{ steps.my-action.outputs.my-result }} in the
|
|
27
|
+
calling workflow evaluates to empty string with no error or warning.
|
|
28
|
+
|
|
29
|
+
This is different from regular workflow jobs, where step outputs are
|
|
30
|
+
accessible within the same job via steps.<id>.outputs.<name> without any
|
|
31
|
+
explicit mapping. Composite actions require explicit wiring because the
|
|
32
|
+
action boundary creates an isolation layer — internal step IDs are not
|
|
33
|
+
exposed to callers.
|
|
34
|
+
|
|
35
|
+
Common mistake pattern: developer tests the composite action, sees that
|
|
36
|
+
internal steps set outputs correctly, but the caller always receives "".
|
|
37
|
+
The action.yml outputs: block is missing or references a wrong step id.
|
|
38
|
+
fix: |
|
|
39
|
+
1. Open the composite action's action.yml file.
|
|
40
|
+
2. Add an outputs: block (after the inputs: block if present).
|
|
41
|
+
3. For each output you need to expose, add an entry with:
|
|
42
|
+
value: ${{ steps.<internal-step-id>.outputs.<output-name> }}
|
|
43
|
+
4. Ensure the referenced internal step has a matching id: field.
|
|
44
|
+
5. Ensure the internal step writes outputs via GITHUB_OUTPUT:
|
|
45
|
+
echo "result=value" >> $GITHUB_OUTPUT
|
|
46
|
+
fix_code:
|
|
47
|
+
- language: yaml
|
|
48
|
+
label: 'action.yml — declare outputs with value expressions'
|
|
49
|
+
code: |
|
|
50
|
+
# .github/actions/my-build/action.yml
|
|
51
|
+
name: 'My Build Action'
|
|
52
|
+
description: 'Builds the project and returns the version'
|
|
53
|
+
|
|
54
|
+
outputs:
|
|
55
|
+
build-version:
|
|
56
|
+
description: 'The version that was built'
|
|
57
|
+
value: ${{ steps.get-version.outputs.version }}
|
|
58
|
+
artifact-path:
|
|
59
|
+
description: 'Path to the built artifact'
|
|
60
|
+
value: ${{ steps.build.outputs.artifact }}
|
|
61
|
+
|
|
62
|
+
runs:
|
|
63
|
+
using: composite
|
|
64
|
+
steps:
|
|
65
|
+
- id: get-version
|
|
66
|
+
shell: bash
|
|
67
|
+
run: echo "version=$(cat version.txt)" >> $GITHUB_OUTPUT
|
|
68
|
+
|
|
69
|
+
- id: build
|
|
70
|
+
shell: bash
|
|
71
|
+
run: |
|
|
72
|
+
make build
|
|
73
|
+
echo "artifact=dist/app.tar.gz" >> $GITHUB_OUTPUT
|
|
74
|
+
- language: yaml
|
|
75
|
+
label: 'Calling workflow — accessing composite action outputs'
|
|
76
|
+
code: |
|
|
77
|
+
jobs:
|
|
78
|
+
release:
|
|
79
|
+
runs-on: ubuntu-latest
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@v4
|
|
82
|
+
|
|
83
|
+
- id: build
|
|
84
|
+
uses: ./.github/actions/my-build
|
|
85
|
+
|
|
86
|
+
- name: Use outputs
|
|
87
|
+
run: |
|
|
88
|
+
echo "Version: ${{ steps.build.outputs.build-version }}"
|
|
89
|
+
echo "Artifact: ${{ steps.build.outputs.artifact-path }}"
|
|
90
|
+
prevention:
|
|
91
|
+
- 'Every composite action output must have a corresponding entry in the outputs: block of action.yml'
|
|
92
|
+
- 'Internal steps must have an id: field for the outputs value: expression to reference'
|
|
93
|
+
- 'The step id in the value: expression must exactly match the internal step id: value'
|
|
94
|
+
- 'Add a debug step in the action to print $GITHUB_OUTPUT contents when troubleshooting'
|
|
95
|
+
docs:
|
|
96
|
+
- url: 'https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#outputs-for-composite-actions'
|
|
97
|
+
label: 'GitHub Docs — Outputs for composite actions'
|
|
98
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs'
|
|
99
|
+
label: 'GitHub Docs — Passing information between jobs'
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
id: silent-failures-047
|
|
2
|
+
title: "GITHUB_ENV variables are job-scoped — not shared with downstream needs: jobs"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- GITHUB_ENV
|
|
7
|
+
- environment-variables
|
|
8
|
+
- job-outputs
|
|
9
|
+
- cross-job
|
|
10
|
+
- silent-failure
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'GITHUB_ENV'
|
|
13
|
+
flags: ''
|
|
14
|
+
- regex: 'echo\s+".+=.+"\s*>>\s*\$GITHUB_ENV'
|
|
15
|
+
flags: i
|
|
16
|
+
error_messages: []
|
|
17
|
+
root_cause: |
|
|
18
|
+
Environment variables written to $GITHUB_ENV are scoped to the CURRENT JOB
|
|
19
|
+
only. They persist across all subsequent steps within that job, but are NOT
|
|
20
|
+
propagated to any other job — including jobs that depend on the producing
|
|
21
|
+
job via needs:.
|
|
22
|
+
|
|
23
|
+
This is a silent failure because:
|
|
24
|
+
1. The producing step exits 0 with no warning
|
|
25
|
+
2. The downstream job runs normally with no error message
|
|
26
|
+
3. The variable evaluates to empty string in the downstream job
|
|
27
|
+
4. Steps that depend on the value produce wrong results or silently skip
|
|
28
|
+
based on the empty string condition
|
|
29
|
+
|
|
30
|
+
Common scenario (silently broken):
|
|
31
|
+
Job A: echo "VERSION=1.2.3" >> $GITHUB_ENV
|
|
32
|
+
Job B (needs: job-a): echo $VERSION → prints empty string
|
|
33
|
+
|
|
34
|
+
The correct mechanism for sharing values across job boundaries is job outputs
|
|
35
|
+
combined with the needs context. GITHUB_ENV is appropriate only for values
|
|
36
|
+
that need to persist across multiple steps within the same job.
|
|
37
|
+
|
|
38
|
+
Source: GitHub Docs — Passing information between jobs; GitHub Community and
|
|
39
|
+
Stack Overflow cross-job environment variable questions.
|
|
40
|
+
fix: |
|
|
41
|
+
To pass a value from one job to a downstream job:
|
|
42
|
+
|
|
43
|
+
1. Write the value to $GITHUB_OUTPUT (not $GITHUB_ENV) in the producing step
|
|
44
|
+
2. Declare the step output in the producing job's outputs: block
|
|
45
|
+
3. Reference the value in the consuming job via ${{ needs.job-id.outputs.key }}
|
|
46
|
+
|
|
47
|
+
Continue using $GITHUB_ENV when the value only needs to be available to later
|
|
48
|
+
steps within the same job.
|
|
49
|
+
fix_code:
|
|
50
|
+
- language: yaml
|
|
51
|
+
label: "Correct cross-job value sharing via GITHUB_OUTPUT and job outputs"
|
|
52
|
+
code: |
|
|
53
|
+
jobs:
|
|
54
|
+
build:
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
outputs:
|
|
57
|
+
# Expose the step output as a job output
|
|
58
|
+
version: ${{ steps.get-version.outputs.version }}
|
|
59
|
+
steps:
|
|
60
|
+
- id: get-version
|
|
61
|
+
# Write to GITHUB_OUTPUT for cross-job sharing, not GITHUB_ENV
|
|
62
|
+
run: echo "version=1.2.3" >> $GITHUB_OUTPUT
|
|
63
|
+
|
|
64
|
+
deploy:
|
|
65
|
+
needs: build
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
steps:
|
|
68
|
+
- name: Use version from build job
|
|
69
|
+
run: echo "Deploying version ${{ needs.build.outputs.version }}"
|
|
70
|
+
- language: yaml
|
|
71
|
+
label: "GITHUB_ENV is correct for within-job cross-step sharing"
|
|
72
|
+
code: |
|
|
73
|
+
jobs:
|
|
74
|
+
build:
|
|
75
|
+
runs-on: ubuntu-latest
|
|
76
|
+
steps:
|
|
77
|
+
- name: Set version for this job's steps
|
|
78
|
+
run: echo "VERSION=1.2.3" >> $GITHUB_ENV
|
|
79
|
+
|
|
80
|
+
- name: Step 2 in same job — $VERSION is available
|
|
81
|
+
run: echo "Building $VERSION"
|
|
82
|
+
|
|
83
|
+
- name: Step 3 in same job — $VERSION is still available
|
|
84
|
+
run: echo "Packaging $VERSION"
|
|
85
|
+
|
|
86
|
+
# $VERSION is NOT available in any other job
|
|
87
|
+
prevention:
|
|
88
|
+
- "Use GITHUB_OUTPUT + job outputs: for any value that must cross a job boundary"
|
|
89
|
+
- "Use GITHUB_ENV only for values shared between steps within the same job"
|
|
90
|
+
- "Add a verification step in the consuming job that echoes the value and fails if empty"
|
|
91
|
+
- "Avoid setting both GITHUB_ENV and GITHUB_OUTPUT for the same value — use GITHUB_OUTPUT as the single source of truth"
|
|
92
|
+
docs:
|
|
93
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs"
|
|
94
|
+
label: "GitHub Docs — Passing information between jobs"
|
|
95
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables"
|
|
96
|
+
label: "GitHub Docs — Storing information in variables (GITHUB_ENV)"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
id: silent-failures-046
|
|
2
|
+
title: "matrix.exclude silently ignored when value doesn't exactly match matrix dimension"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- matrix
|
|
7
|
+
- exclude
|
|
8
|
+
- strategy
|
|
9
|
+
- configuration
|
|
10
|
+
- silent-failure
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'exclude:'
|
|
13
|
+
flags: ''
|
|
14
|
+
- regex: 'strategy:\s*\n\s*matrix:'
|
|
15
|
+
flags: im
|
|
16
|
+
error_messages: []
|
|
17
|
+
root_cause: |
|
|
18
|
+
GitHub Actions matrix exclude: uses exact string equality. An exclude entry
|
|
19
|
+
removes a combination only when ALL specified key-value pairs exactly match
|
|
20
|
+
a generated matrix combination. If any pair fails to match, the combination
|
|
21
|
+
is silently kept.
|
|
22
|
+
|
|
23
|
+
Three common silent-failure patterns:
|
|
24
|
+
|
|
25
|
+
1. Value substring mismatch:
|
|
26
|
+
matrix: {os: [ubuntu-latest, windows-latest]}
|
|
27
|
+
exclude: [{os: ubuntu}] # 'ubuntu' != 'ubuntu-latest' → ignored
|
|
28
|
+
|
|
29
|
+
2. Key not present in matrix dimensions:
|
|
30
|
+
matrix: {os: [ubuntu-latest], node: [18, 20]}
|
|
31
|
+
exclude: [{os: ubuntu-latest, version: 18}] # 'version' not a matrix key → ignored
|
|
32
|
+
|
|
33
|
+
3. Type mismatch in some expression contexts:
|
|
34
|
+
matrix: {node: [18, 20]}
|
|
35
|
+
exclude: [{node: "18"}] # string "18" may not equal integer 18
|
|
36
|
+
|
|
37
|
+
GitHub produces no warning when an exclude entry matches zero combinations.
|
|
38
|
+
The unwanted job continues to run, consuming CI minutes with no indication
|
|
39
|
+
that the exclude rule was ineffective.
|
|
40
|
+
|
|
41
|
+
Source: GitHub Docs matrix strategy; GitHub Community/26957; Stack Overflow
|
|
42
|
+
questions about matrix exclude not working as expected.
|
|
43
|
+
fix: |
|
|
44
|
+
Use the exact string that appears in your matrix definition when writing
|
|
45
|
+
exclude entries. Run a matrix debug step to confirm the actual values
|
|
46
|
+
GitHub generates before relying on exclusions.
|
|
47
|
+
|
|
48
|
+
For complex exclusion logic involving substring matching or multiple
|
|
49
|
+
conditions, use the if: condition on the job instead of exclude:.
|
|
50
|
+
The if: condition can use contains(), startsWith(), and other expression
|
|
51
|
+
functions that exact-match exclude cannot.
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "Correct exclude — exact value match required"
|
|
55
|
+
code: |
|
|
56
|
+
jobs:
|
|
57
|
+
test:
|
|
58
|
+
strategy:
|
|
59
|
+
matrix:
|
|
60
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
61
|
+
node: [18, 20, 22]
|
|
62
|
+
exclude:
|
|
63
|
+
# Value must match EXACTLY what appears in the matrix list above
|
|
64
|
+
- os: windows-latest # ✅ exact match
|
|
65
|
+
node: 18
|
|
66
|
+
# Incorrect examples (silently ignored):
|
|
67
|
+
# - os: windows # ❌ 'windows' != 'windows-latest'
|
|
68
|
+
# - os: windows-latest
|
|
69
|
+
# version: 18 # ❌ 'version' not a matrix dimension key
|
|
70
|
+
runs-on: ${{ matrix.os }}
|
|
71
|
+
steps:
|
|
72
|
+
- name: Debug matrix combination
|
|
73
|
+
run: echo '${{ toJSON(matrix) }}'
|
|
74
|
+
- language: yaml
|
|
75
|
+
label: "Alternative — use if: for flexible exclusion"
|
|
76
|
+
code: |
|
|
77
|
+
jobs:
|
|
78
|
+
test:
|
|
79
|
+
# Use if: when you need startsWith / contains logic
|
|
80
|
+
if: >-
|
|
81
|
+
!(matrix.os == 'windows-latest' && matrix.node == 18)
|
|
82
|
+
strategy:
|
|
83
|
+
matrix:
|
|
84
|
+
os: [ubuntu-latest, windows-latest]
|
|
85
|
+
node: [18, 20]
|
|
86
|
+
runs-on: ${{ matrix.os }}
|
|
87
|
+
prevention:
|
|
88
|
+
- "Use the exact strings from your matrix definition in exclude entries — copy-paste, do not retype"
|
|
89
|
+
- "Add a debug step printing toJSON(matrix) to verify which combinations GitHub actually generates"
|
|
90
|
+
- "Use if: conditions for substring matching, contains(), or multi-condition exclusion logic"
|
|
91
|
+
- "Test matrix changes on a branch and review the job list before merging to confirm excluded combinations are gone"
|
|
92
|
+
docs:
|
|
93
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow#excluding-matrix-configurations"
|
|
94
|
+
label: "GitHub Docs — Excluding matrix configurations"
|
|
95
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/using-conditions-to-control-job-execution"
|
|
96
|
+
label: "GitHub Docs — Using conditions to control job execution"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
id: triggers-032
|
|
2
|
+
title: 'on.workflow_run.workflows matches name: field not filename — mismatch silently never triggers'
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- workflow_run
|
|
7
|
+
- trigger
|
|
8
|
+
- name-mismatch
|
|
9
|
+
- silent
|
|
10
|
+
- filename
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'workflow_run'
|
|
13
|
+
flags: ''
|
|
14
|
+
error_messages: []
|
|
15
|
+
root_cause: |
|
|
16
|
+
The on.workflow_run.workflows: array matches against the target workflow's
|
|
17
|
+
top-level name: field — NOT the workflow's filename.
|
|
18
|
+
|
|
19
|
+
If the target workflow contains:
|
|
20
|
+
name: CI Pipeline
|
|
21
|
+
|
|
22
|
+
then the triggering workflow must specify:
|
|
23
|
+
on:
|
|
24
|
+
workflow_run:
|
|
25
|
+
workflows: ["CI Pipeline"]
|
|
26
|
+
|
|
27
|
+
Using the filename ("build.yml", "ci.yml", or ".github/workflows/ci.yml")
|
|
28
|
+
never matches when a name: field is present. The comparison is case-sensitive:
|
|
29
|
+
"CI" != "ci" != "Ci".
|
|
30
|
+
|
|
31
|
+
Secondary rule: if the target workflow has no name: field, GitHub uses the
|
|
32
|
+
workflow file path relative to the repo root as the match string (e.g.,
|
|
33
|
+
".github/workflows/build.yml").
|
|
34
|
+
|
|
35
|
+
No error or warning is produced on mismatch — the triggering workflow
|
|
36
|
+
silently never fires. This is one of the most common workflow_run
|
|
37
|
+
misconfiguration patterns on Stack Overflow.
|
|
38
|
+
fix: |
|
|
39
|
+
1. Open the target workflow file.
|
|
40
|
+
2. Find the top-level name: field. If absent, note the repo-relative path.
|
|
41
|
+
3. Use that exact string (case-sensitive) in the workflows: array.
|
|
42
|
+
4. Merge/deploy the triggering workflow to the default branch — workflow_run
|
|
43
|
+
only fires when the trigger configuration itself is on the default branch.
|
|
44
|
+
fix_code:
|
|
45
|
+
- language: yaml
|
|
46
|
+
label: 'Correct: match the target workflow name: field exactly'
|
|
47
|
+
code: |
|
|
48
|
+
# Target workflow file: .github/workflows/ci.yml
|
|
49
|
+
name: CI Pipeline # <-- this is what workflow_run matches on
|
|
50
|
+
|
|
51
|
+
on: [push]
|
|
52
|
+
jobs:
|
|
53
|
+
build:
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- run: echo "building"
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
# Triggering workflow file: .github/workflows/deploy.yml
|
|
60
|
+
on:
|
|
61
|
+
workflow_run:
|
|
62
|
+
workflows: ["CI Pipeline"] # Must match name: exactly (not "ci.yml")
|
|
63
|
+
types: [completed]
|
|
64
|
+
jobs:
|
|
65
|
+
deploy:
|
|
66
|
+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
steps:
|
|
69
|
+
- run: echo "deploying"
|
|
70
|
+
- language: yaml
|
|
71
|
+
label: 'When target workflow has no name: field, use the file path'
|
|
72
|
+
code: |
|
|
73
|
+
# Target workflow has no name: field at .github/workflows/build.yml
|
|
74
|
+
on: [push]
|
|
75
|
+
jobs:
|
|
76
|
+
build:
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
steps:
|
|
79
|
+
- run: echo "building"
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
# Triggering workflow must use the repo-relative path
|
|
83
|
+
on:
|
|
84
|
+
workflow_run:
|
|
85
|
+
workflows: [".github/workflows/build.yml"]
|
|
86
|
+
types: [completed]
|
|
87
|
+
prevention:
|
|
88
|
+
- 'Always add a name: field to every workflow you intend to reference in workflow_run'
|
|
89
|
+
- 'Copy the name: value exactly (including capitalization and spaces) into workflows:'
|
|
90
|
+
- 'Add a comment in the triggering workflow citing which file the name came from'
|
|
91
|
+
- 'workflow_run silently never fires on name mismatches — no diagnostic is emitted'
|
|
92
|
+
docs:
|
|
93
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run'
|
|
94
|
+
label: 'GitHub Docs — workflow_run trigger'
|
|
95
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#name'
|
|
96
|
+
label: 'GitHub Docs — Workflow name: field'
|
package/package.json
CHANGED