@htekdev/actions-debugger 1.0.123 → 1.0.125
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/errors/caching-artifacts/caching-artifacts-073.yml +100 -0
- package/errors/caching-artifacts/caching-artifacts-074.yml +117 -0
- package/errors/known-unsolved/known-unsolved-070.yml +83 -0
- package/errors/known-unsolved/known-unsolved-071.yml +122 -0
- package/errors/known-unsolved/known-unsolved-072.yml +143 -0
- package/errors/permissions-auth/permissions-auth-071.yml +144 -0
- package/errors/permissions-auth/permissions-auth-072.yml +112 -0
- package/errors/permissions-auth/permissions-auth-073.yml +127 -0
- package/errors/permissions-auth/permissions-auth-074.yml +106 -0
- package/errors/permissions-auth/permissions-auth-075.yml +137 -0
- package/errors/runner-environment/runner-environment-224.yml +74 -0
- package/errors/runner-environment/runner-environment-225.yml +85 -0
- package/errors/runner-environment/runner-environment-226.yml +91 -0
- package/errors/runner-environment/runner-environment-227.yml +106 -0
- package/errors/runner-environment/runner-environment-228.yml +117 -0
- package/errors/runner-environment/runner-environment-229.yml +119 -0
- package/errors/runner-environment/runner-environment-230.yml +129 -0
- package/errors/runner-environment/runner-environment-231.yml +90 -0
- package/errors/runner-environment/runner-environment-232.yml +131 -0
- package/errors/runner-environment/runner-environment-233.yml +90 -0
- package/errors/runner-environment/runner-environment-234.yml +114 -0
- package/errors/runner-environment/runner-environment-235.yml +151 -0
- package/errors/silent-failures/silent-failures-112.yml +97 -0
- package/errors/silent-failures/silent-failures-113.yml +110 -0
- package/errors/silent-failures/silent-failures-114.yml +116 -0
- package/errors/silent-failures/silent-failures-115.yml +130 -0
- package/errors/silent-failures/silent-failures-116.yml +117 -0
- package/errors/silent-failures/silent-failures-117.yml +137 -0
- package/errors/silent-failures/silent-failures-118.yml +156 -0
- package/errors/yaml-syntax/yaml-syntax-075.yml +128 -0
- package/errors/yaml-syntax/yaml-syntax-076.yml +107 -0
- package/package.json +1 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
id: silent-failures-116
|
|
2
|
+
title: 'actions/checkout sparse-checkout silently falls back to full REST API download when git < 2.28'
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- checkout
|
|
7
|
+
- sparse-checkout
|
|
8
|
+
- git-version
|
|
9
|
+
- self-hosted
|
|
10
|
+
- rest-api-fallback
|
|
11
|
+
- silent
|
|
12
|
+
- git
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'Minimum Git version required for sparse checkout is 2\.28'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'Failed to initialize CommandManager.*sparse'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
- regex: 'Falling back to downloading using the GitHub REST API'
|
|
19
|
+
flags: 'i'
|
|
20
|
+
error_messages:
|
|
21
|
+
- 'Minimum Git version required for sparse checkout is 2.28. Your git (/path/to/git) is 2.23'
|
|
22
|
+
- 'Falling back to downloading using the GitHub REST API'
|
|
23
|
+
- 'Warning: The git version installed on this runner is 2.x which is lower than the minimum required version 2.28 for sparse checkout'
|
|
24
|
+
root_cause: |
|
|
25
|
+
actions/checkout supports `sparse-checkout` configuration, which requires git 2.28 or
|
|
26
|
+
later (because sparse-checkout with cone-mode patterns uses `git sparse-checkout init`,
|
|
27
|
+
introduced in that version).
|
|
28
|
+
|
|
29
|
+
When checkout attempts to initialise the git CommandManager and git is present on the
|
|
30
|
+
runner but too old to satisfy the sparse-checkout requirement, the action's
|
|
31
|
+
`getGitCommandManager` function throws an error. However, this error is caught in a
|
|
32
|
+
broad try/catch block:
|
|
33
|
+
|
|
34
|
+
} catch (err) {
|
|
35
|
+
// Git is required for LFS
|
|
36
|
+
if (settings.lfs) { throw err }
|
|
37
|
+
// otherwise: silently continue without git
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Because `lfs` is not enabled, the error is swallowed without being logged to the
|
|
41
|
+
workflow output. Checkout falls back to downloading the repository as a ZIP archive
|
|
42
|
+
via the GitHub REST API and extracts it into the workspace.
|
|
43
|
+
|
|
44
|
+
The result is a **full clone** of the repository (subject to `fetch-depth`), not a
|
|
45
|
+
sparse checkout. All files are present in the workspace instead of only the requested
|
|
46
|
+
sparse paths. Workflows relying on sparse checkout to reduce disk usage or build time
|
|
47
|
+
proceed with the full repository content, potentially causing subtle downstream failures
|
|
48
|
+
(out-of-disk, dependency version mismatches, unexpected files in build artefacts) with
|
|
49
|
+
no indication that sparse-checkout was skipped.
|
|
50
|
+
|
|
51
|
+
This primarily affects **self-hosted runners** where a locally installed git binary is
|
|
52
|
+
older than 2.28 (e.g., git 2.17 shipped with Ubuntu 18.04, or git 2.23 on Amazon Linux
|
|
53
|
+
running in an older container). GitHub-hosted runners ship git 2.43+ and are not affected.
|
|
54
|
+
|
|
55
|
+
Reported in actions/checkout#2435 (May 2026).
|
|
56
|
+
fix: |
|
|
57
|
+
**Step 1: Identify the failure.**
|
|
58
|
+
Add a step to print the git version before checkout:
|
|
59
|
+
- run: git --version
|
|
60
|
+
|
|
61
|
+
If the version is below 2.28, that is the root cause of the silent fallback.
|
|
62
|
+
|
|
63
|
+
**Step 2: Upgrade git on self-hosted runners.**
|
|
64
|
+
Install a modern git version (2.28+) on the runner. For Ubuntu:
|
|
65
|
+
sudo add-apt-repository ppa:git-core/ppa
|
|
66
|
+
sudo apt-get update && sudo apt-get install -y git
|
|
67
|
+
|
|
68
|
+
**Step 3: Until git is upgraded, disable sparse-checkout.**
|
|
69
|
+
Remove or guard the `sparse-checkout` input to avoid the silent fallback:
|
|
70
|
+
sparse-checkout: ''
|
|
71
|
+
|
|
72
|
+
**Alternative: require a minimum git version in the workflow.**
|
|
73
|
+
Fail fast with an explicit error rather than a silent fallback by checking the version
|
|
74
|
+
before the checkout step.
|
|
75
|
+
fix_code:
|
|
76
|
+
- language: yaml
|
|
77
|
+
label: 'Add explicit git version gate before checkout with sparse-checkout'
|
|
78
|
+
code: |
|
|
79
|
+
- name: Verify git version supports sparse-checkout
|
|
80
|
+
run: |
|
|
81
|
+
GIT_VERSION=$(git --version | awk '{print $3}')
|
|
82
|
+
MIN_VERSION="2.28.0"
|
|
83
|
+
if [ "$(printf '%s\n' "$MIN_VERSION" "$GIT_VERSION" | sort -V | head -n1)" != "$MIN_VERSION" ]; then
|
|
84
|
+
echo "::error::git $GIT_VERSION is too old for sparse-checkout (requires >= 2.28)"
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
- uses: actions/checkout@v6
|
|
89
|
+
with:
|
|
90
|
+
sparse-checkout: |
|
|
91
|
+
src/
|
|
92
|
+
tests/
|
|
93
|
+
- language: yaml
|
|
94
|
+
label: 'Upgrade git on Ubuntu self-hosted runner before checkout'
|
|
95
|
+
code: |
|
|
96
|
+
- name: Ensure git >= 2.28 for sparse-checkout support
|
|
97
|
+
run: |
|
|
98
|
+
sudo add-apt-repository -y ppa:git-core/ppa
|
|
99
|
+
sudo apt-get update -q
|
|
100
|
+
sudo apt-get install -y git
|
|
101
|
+
git --version
|
|
102
|
+
|
|
103
|
+
- uses: actions/checkout@v6
|
|
104
|
+
with:
|
|
105
|
+
sparse-checkout: |
|
|
106
|
+
src/
|
|
107
|
+
prevention:
|
|
108
|
+
- 'Always confirm the git version on self-hosted runners meets the minimum requirements for checkout features you use; add a preflight check step if runners may have older git installs'
|
|
109
|
+
- 'Pin to a specific version of actions/checkout and audit the changelog when upgrading — the sparse-checkout silent fallback has existed across multiple major versions'
|
|
110
|
+
- 'If disk usage is a concern and sparse-checkout is required, add a post-checkout assertion that only the expected sparse paths exist, which will catch the REST API fallback'
|
|
111
|
+
docs:
|
|
112
|
+
- url: 'https://github.com/actions/checkout/issues/2435'
|
|
113
|
+
label: 'actions/checkout#2435 — Errors from initializing gitCommandManager are silently ignored'
|
|
114
|
+
- url: 'https://github.com/actions/checkout#usage'
|
|
115
|
+
label: 'actions/checkout README — sparse-checkout input and requirements'
|
|
116
|
+
- url: 'https://git-scm.com/docs/git-sparse-checkout'
|
|
117
|
+
label: 'Git documentation — git-sparse-checkout (requires git 2.25+, cone mode 2.26+)'
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
id: silent-failures-117
|
|
2
|
+
title: 'if: always() runs steps even when the workflow is manually cancelled — use success() || failure() to respect cancellation'
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- if-condition
|
|
7
|
+
- always
|
|
8
|
+
- cancelled
|
|
9
|
+
- cleanup
|
|
10
|
+
- notification
|
|
11
|
+
- status-check
|
|
12
|
+
- silent-failure
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'if:\s*always\(\)'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'if:\s*\$\{\{\s*always\(\)\s*\}\}'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
error_messages:
|
|
19
|
+
- '(no error emitted — steps run unexpectedly during workflow cancellation)'
|
|
20
|
+
root_cause: |
|
|
21
|
+
Developers commonly add `if: always()` to steps or jobs that should run even when
|
|
22
|
+
a previous step fails — for example, uploading test reports after a failed test run,
|
|
23
|
+
or sending failure notifications.
|
|
24
|
+
|
|
25
|
+
The silent failure is that `always()` evaluates to `true` in ALL cases including
|
|
26
|
+
when the workflow is manually cancelled. When a user or a concurrency group cancellation
|
|
27
|
+
triggers, any step or job with `if: always()` still executes. This is often unintended:
|
|
28
|
+
the developer expected "run on failure" not "run even when someone cancels".
|
|
29
|
+
|
|
30
|
+
GitHub Actions documentation explicitly warns about this:
|
|
31
|
+
"Consider using if: !cancelled() if you want the step or job to continue
|
|
32
|
+
executing when the current run is cancelled. [...] Using always() is not
|
|
33
|
+
recommended as it causes a step to run when a workflow is cancelled, which
|
|
34
|
+
can result in unintended behavior."
|
|
35
|
+
|
|
36
|
+
Common scenarios where this causes problems:
|
|
37
|
+
- Test report upload step runs when a PR author cancels a run, unnecessarily consuming
|
|
38
|
+
runner minutes and potentially posting partial results to a PR check.
|
|
39
|
+
- Slack/PagerDuty notification fires for a manual cancellation, producing false alerts.
|
|
40
|
+
- Deploy rollback job runs during a cancellation of a non-deployment workflow, triggering
|
|
41
|
+
rollback unexpectedly.
|
|
42
|
+
- Cleanup job executes with only partial data because the workflow was cut short.
|
|
43
|
+
|
|
44
|
+
The correct alternative for "run when success OR failure, but NOT when cancelled" is:
|
|
45
|
+
if: success() || failure()
|
|
46
|
+
or equivalently:
|
|
47
|
+
if: ${{ !cancelled() }}
|
|
48
|
+
|
|
49
|
+
Source: Stack Overflow q/58858429 (415 upvotes, 327k views), GitHub Docs always() warning,
|
|
50
|
+
test-summary/action#51 community discussion (Jan 2025).
|
|
51
|
+
fix: |
|
|
52
|
+
Replace `if: always()` with `if: success() || failure()` for steps and jobs that
|
|
53
|
+
should run on success or failure but NOT when the workflow is cancelled.
|
|
54
|
+
|
|
55
|
+
Use `if: always()` only when you explicitly want the step to run even during
|
|
56
|
+
cancellation — for example, an emergency teardown that must run regardless.
|
|
57
|
+
|
|
58
|
+
Three options depending on intent:
|
|
59
|
+
|
|
60
|
+
1. Run on success or failure (skip on cancel): `if: success() || failure()`
|
|
61
|
+
2. Run on any non-success outcome (skip on cancel): `if: failure()`
|
|
62
|
+
3. Run always including on cancel (current behaviour): `if: always()`
|
|
63
|
+
|
|
64
|
+
GitHub also documents `if: ${{ !cancelled() }}` as equivalent to option 1 in
|
|
65
|
+
most contexts.
|
|
66
|
+
fix_code:
|
|
67
|
+
- language: yaml
|
|
68
|
+
label: 'Before: if: always() fires even on manual cancellation'
|
|
69
|
+
code: |
|
|
70
|
+
jobs:
|
|
71
|
+
test:
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- run: pytest --junitxml=results.xml
|
|
75
|
+
|
|
76
|
+
- name: Upload test results
|
|
77
|
+
if: always() # fires even when the job is cancelled mid-run
|
|
78
|
+
uses: actions/upload-artifact@v4
|
|
79
|
+
with:
|
|
80
|
+
name: test-results
|
|
81
|
+
path: results.xml
|
|
82
|
+
- language: yaml
|
|
83
|
+
label: 'After: if: success() || failure() respects cancellation'
|
|
84
|
+
code: |
|
|
85
|
+
jobs:
|
|
86
|
+
test:
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
steps:
|
|
89
|
+
- run: pytest --junitxml=results.xml
|
|
90
|
+
|
|
91
|
+
- name: Upload test results
|
|
92
|
+
if: success() || failure() # runs on pass or fail; skips on cancel
|
|
93
|
+
uses: actions/upload-artifact@v4
|
|
94
|
+
with:
|
|
95
|
+
name: test-results
|
|
96
|
+
path: results.xml
|
|
97
|
+
- language: yaml
|
|
98
|
+
label: 'Notification job: skip Slack alert on cancellation'
|
|
99
|
+
code: |
|
|
100
|
+
jobs:
|
|
101
|
+
build:
|
|
102
|
+
runs-on: ubuntu-latest
|
|
103
|
+
steps:
|
|
104
|
+
- run: make build
|
|
105
|
+
|
|
106
|
+
notify:
|
|
107
|
+
needs: build
|
|
108
|
+
# success() || failure() fires on build success or failure, not on cancel
|
|
109
|
+
if: success() || failure()
|
|
110
|
+
runs-on: ubuntu-latest
|
|
111
|
+
steps:
|
|
112
|
+
- name: Notify Slack
|
|
113
|
+
run: |
|
|
114
|
+
echo "Build result: ${{ needs.build.result }}"
|
|
115
|
+
# Sends alert only on success or failure — not on manual cancel
|
|
116
|
+
|
|
117
|
+
# Only use always() when you MUST run on cancellation too:
|
|
118
|
+
emergency-cleanup:
|
|
119
|
+
needs: build
|
|
120
|
+
if: always() # intentional: must clean up even if cancelled
|
|
121
|
+
runs-on: ubuntu-latest
|
|
122
|
+
timeout-minutes: 4 # stay under the 5-min forced-kill window
|
|
123
|
+
steps:
|
|
124
|
+
- run: ./cleanup.sh
|
|
125
|
+
prevention:
|
|
126
|
+
- 'Default to `if: success() || failure()` for upload, notification, and cleanup steps — reserve `if: always()` only for teardown steps that truly must run on cancellation'
|
|
127
|
+
- 'Remember: `always()` is not "run when failure"; it is "run even when cancelled" — the distinction matters for notification jobs'
|
|
128
|
+
- 'GitHub docs recommend `if: ${{ !cancelled() }}` as an alternative to `if: success() || failure()` for the same effect'
|
|
129
|
+
- 'Combine `if: always()` with `timeout-minutes:` on any job that runs after cancellation; GitHub force-kills all jobs after 5 minutes of the cancellation signal'
|
|
130
|
+
- 'Run a quick cancellation test: manually cancel the workflow and verify whether notification steps fire unexpectedly'
|
|
131
|
+
docs:
|
|
132
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#always'
|
|
133
|
+
label: 'GitHub Docs: always() status check function — cancellation warning'
|
|
134
|
+
- url: 'https://stackoverflow.com/questions/58858429/how-to-run-a-github-actions-step-even-if-the-previous-step-fails-while-still-failing-the-job'
|
|
135
|
+
label: 'Stack Overflow 58858429: Run step on failure while still failing the job (415 votes)'
|
|
136
|
+
- url: 'https://github.com/test-summary/action/issues/51'
|
|
137
|
+
label: 'test-summary/action#51: Use success() || failure() instead of always() to prevent running on cancel'
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
id: silent-failures-118
|
|
2
|
+
title: 'Reusable workflow jobs.<name>.result is always empty string in on.workflow_call.outputs value'
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- reusable-workflow
|
|
7
|
+
- workflow_call
|
|
8
|
+
- outputs
|
|
9
|
+
- jobs-result
|
|
10
|
+
- empty-string
|
|
11
|
+
- silent-failure
|
|
12
|
+
- job-outcome
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'value:\s*\$\{\{\s*jobs\.[a-zA-Z0-9_-]+\.result\s*\}\}'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'needs\.[a-zA-Z0-9_-]+\.outputs\.[a-zA-Z0-9_-]+.*result'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
error_messages:
|
|
19
|
+
- '(no error emitted — needs.<job>.outputs.<name> resolves to empty string in the caller)'
|
|
20
|
+
root_cause: |
|
|
21
|
+
GitHub Actions documentation states that the `jobs` context is available in
|
|
22
|
+
reusable workflows and includes `jobs.<job_id>.result` — the job conclusion value
|
|
23
|
+
("success", "failure", "cancelled", or "skipped"). Developers use this to expose job
|
|
24
|
+
outcomes to calling workflows via `on.workflow_call.outputs`.
|
|
25
|
+
|
|
26
|
+
However, `jobs.<job_id>.result` does NOT work when used in the `value:` expression of
|
|
27
|
+
`on.workflow_call.outputs`. It always resolves to an empty string ("") in the caller's
|
|
28
|
+
`needs.<called_job>.outputs.<name>`.
|
|
29
|
+
|
|
30
|
+
This is distinct from the two-level output declaration issue (sf-060) where developers
|
|
31
|
+
forget to declare outputs at the workflow_call level. Here the developer correctly
|
|
32
|
+
declares both levels, but uses `jobs.<name>.result` (the job outcome) instead of
|
|
33
|
+
`jobs.<name>.outputs.<step-output>` (a custom output). The `.result` property is
|
|
34
|
+
simply not available via this mechanism.
|
|
35
|
+
|
|
36
|
+
Workarounds discovered by the community:
|
|
37
|
+
1. Explicitly capture the outcome as a step output and then chain it through job outputs:
|
|
38
|
+
steps:
|
|
39
|
+
- id: capture
|
|
40
|
+
run: echo "result=${{ job.status }}" >> "$GITHUB_OUTPUT"
|
|
41
|
+
outputs:
|
|
42
|
+
result: ${{ steps.capture.outputs.result }}
|
|
43
|
+
2. Use `fromJSON(toJSON(jobs.build)).result` — this works because it forces the
|
|
44
|
+
expression evaluator to serialise and deserialise the jobs context object, which
|
|
45
|
+
happens to populate the result at evaluation time. This is an accidental workaround
|
|
46
|
+
and not guaranteed to remain stable.
|
|
47
|
+
|
|
48
|
+
The root cause is that the `jobs.<job_id>.result` property in the workflow_call outputs
|
|
49
|
+
`value:` expression is evaluated before job conclusions are fully committed to the
|
|
50
|
+
expression context, unlike `jobs.<job_id>.outputs.<name>` which goes through a different
|
|
51
|
+
code path.
|
|
52
|
+
|
|
53
|
+
Source: actions/runner#2495 (open since 2023, multiple upvotes),
|
|
54
|
+
actions/runner#3087 (Cannot access jobs.<id>.result from on.workflow_call.outputs..value).
|
|
55
|
+
fix: |
|
|
56
|
+
Do NOT use `value: ${{ jobs.<name>.result }}` in on.workflow_call.outputs — it will
|
|
57
|
+
always be empty in the caller.
|
|
58
|
+
|
|
59
|
+
**Option 1 (recommended): Capture outcome via a step output.**
|
|
60
|
+
Add a final step that writes `job.status` to GITHUB_OUTPUT, then chain it up:
|
|
61
|
+
step output → job output → workflow_call output
|
|
62
|
+
|
|
63
|
+
**Option 2: Use fromJSON(toJSON(jobs.<name>)).result workaround.**
|
|
64
|
+
The fromJSON/toJSON wrapper forces the jobs context to be serialised at evaluation
|
|
65
|
+
time, which makes .result available. This is a workaround — prefer option 1.
|
|
66
|
+
|
|
67
|
+
**Option 3: Control caller behavior via job success/failure instead of reading result.**
|
|
68
|
+
If you only need to run downstream caller jobs conditionally based on the reusable
|
|
69
|
+
workflow succeeding or failing, use `if: needs.<job>.result == 'success'` — the
|
|
70
|
+
`needs.<job>.result` in the CALLER is always correct and does NOT need to go through
|
|
71
|
+
workflow_call outputs.
|
|
72
|
+
fix_code:
|
|
73
|
+
- language: yaml
|
|
74
|
+
label: 'Broken: value: ${{ jobs.build.result }} is always empty in caller'
|
|
75
|
+
code: |
|
|
76
|
+
# .github/workflows/reusable.yml — BROKEN pattern
|
|
77
|
+
on:
|
|
78
|
+
workflow_call:
|
|
79
|
+
outputs:
|
|
80
|
+
build-result:
|
|
81
|
+
description: 'Job outcome of the build job'
|
|
82
|
+
value: ${{ jobs.build.result }} # always empty string in caller
|
|
83
|
+
|
|
84
|
+
jobs:
|
|
85
|
+
build:
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
steps:
|
|
88
|
+
- run: make build
|
|
89
|
+
- language: yaml
|
|
90
|
+
label: 'Fixed: capture outcome via step output and chain it through job outputs'
|
|
91
|
+
code: |
|
|
92
|
+
# .github/workflows/reusable.yml — CORRECT pattern
|
|
93
|
+
on:
|
|
94
|
+
workflow_call:
|
|
95
|
+
outputs:
|
|
96
|
+
build-result:
|
|
97
|
+
description: 'Job outcome of the build job'
|
|
98
|
+
value: ${{ jobs.build.outputs.result }} # works correctly
|
|
99
|
+
|
|
100
|
+
jobs:
|
|
101
|
+
build:
|
|
102
|
+
runs-on: ubuntu-latest
|
|
103
|
+
outputs:
|
|
104
|
+
result: ${{ steps.capture.outputs.result }}
|
|
105
|
+
steps:
|
|
106
|
+
- run: make build
|
|
107
|
+
|
|
108
|
+
- id: capture
|
|
109
|
+
if: always()
|
|
110
|
+
shell: bash
|
|
111
|
+
run: echo "result=${{ job.status }}" >> "$GITHUB_OUTPUT"
|
|
112
|
+
- language: yaml
|
|
113
|
+
label: 'Alternative workaround: fromJSON(toJSON(jobs.build)).result (fragile, prefer option 1)'
|
|
114
|
+
code: |
|
|
115
|
+
# .github/workflows/reusable.yml — accidental workaround (not recommended)
|
|
116
|
+
on:
|
|
117
|
+
workflow_call:
|
|
118
|
+
outputs:
|
|
119
|
+
build-result:
|
|
120
|
+
value: ${{ fromJSON(toJSON(jobs.build)).result }}
|
|
121
|
+
|
|
122
|
+
jobs:
|
|
123
|
+
build:
|
|
124
|
+
runs-on: ubuntu-latest
|
|
125
|
+
steps:
|
|
126
|
+
- run: make build
|
|
127
|
+
- language: yaml
|
|
128
|
+
label: 'Caller: reading needs.<job>.result directly works and does NOT need workflow_call outputs'
|
|
129
|
+
code: |
|
|
130
|
+
# .github/workflows/caller.yml
|
|
131
|
+
# needs.<job>.result in the CALLER is always correct — no need to pass it through outputs
|
|
132
|
+
jobs:
|
|
133
|
+
call-build:
|
|
134
|
+
uses: ./.github/workflows/reusable.yml
|
|
135
|
+
|
|
136
|
+
deploy:
|
|
137
|
+
needs: call-build
|
|
138
|
+
# This works correctly — reads the reusable workflow conclusion directly
|
|
139
|
+
if: needs.call-build.result == 'success'
|
|
140
|
+
runs-on: ubuntu-latest
|
|
141
|
+
steps:
|
|
142
|
+
- run: ./deploy.sh
|
|
143
|
+
prevention:
|
|
144
|
+
- 'Never use `value: ${{ jobs.<name>.result }}` in workflow_call outputs — capture it as a step output first'
|
|
145
|
+
- 'If you only need to gate downstream caller jobs on reusable workflow success/failure, use `needs.<job>.result` in the caller directly — it works without going through workflow_call outputs'
|
|
146
|
+
- 'Test output values by printing them in the caller with `run: echo "${{ needs.<job>.outputs.<name> }}"` before relying on them in logic'
|
|
147
|
+
- 'The `fromJSON(toJSON(...)).result` workaround works today but is fragile — prefer the step-output capture pattern'
|
|
148
|
+
docs:
|
|
149
|
+
- url: 'https://github.com/actions/runner/issues/2495'
|
|
150
|
+
label: 'actions/runner#2495: Reusable workflow does not output individual job result'
|
|
151
|
+
- url: 'https://github.com/actions/runner/issues/3087'
|
|
152
|
+
label: 'actions/runner#3087: Cannot access jobs.<id>.result from on.workflow_call.outputs..value'
|
|
153
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onworkflow_calloutputs'
|
|
154
|
+
label: 'GitHub Docs: on.workflow_call.outputs syntax reference'
|
|
155
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#jobs-context'
|
|
156
|
+
label: 'GitHub Docs: jobs context (available only in reusable workflows)'
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
id: yaml-syntax-075
|
|
2
|
+
title: 'environment: shorthand string format silently rejects needs.* and jobs.* contexts — use environment.name form'
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- environment
|
|
7
|
+
- dynamic-environment
|
|
8
|
+
- needs-context
|
|
9
|
+
- expression
|
|
10
|
+
- shorthand
|
|
11
|
+
- template-validation
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Unrecognized named-value.*needs.*environment'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: "Unrecognized named-value: 'needs'.*position.*expression.*needs\\."
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'TemplateValidationException.*Unrecognized named-value.*needs'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- "The workflow is not valid. .github/workflows/deploy.yml (Line: 15, Col: 18): Unrecognized named-value: 'needs'. Located at position 1 within expression: needs.set_environment.outputs.my_env"
|
|
21
|
+
- "Unrecognized named-value: 'needs'. Located at position 1 within expression: needs.X.outputs.env"
|
|
22
|
+
root_cause: |
|
|
23
|
+
The GitHub Actions workflow parser supports two forms for specifying a job environment:
|
|
24
|
+
|
|
25
|
+
1. Shorthand string: `environment: production` or `environment: ${{ some.expression }}`
|
|
26
|
+
2. Explicit mapping: `environment:\n name: ${{ expression }}\n url: ${{ expression }}`
|
|
27
|
+
|
|
28
|
+
The shorthand string form (`environment: ${{ ... }}`) supports only a limited expression
|
|
29
|
+
context and does NOT support the `needs.*` or `jobs.*` contexts, even when the job
|
|
30
|
+
has `needs:` declared. Attempting to use `needs.X.outputs.Y` inside the shorthand
|
|
31
|
+
produces a TemplateValidationException at workflow parse time:
|
|
32
|
+
|
|
33
|
+
"Unrecognized named-value: 'needs'. Located at position 1 within expression: needs.X.outputs.Y"
|
|
34
|
+
|
|
35
|
+
The explicit mapping form (`environment:\n name: ${{ ... }}`) DOES support the `needs.*`
|
|
36
|
+
context fully — this is the documented way to set a dynamic environment name or URL.
|
|
37
|
+
|
|
38
|
+
This trips up developers who:
|
|
39
|
+
- Copy the shorthand form from documentation examples and add an expression.
|
|
40
|
+
- Want to choose between staging/production environments based on the branch output
|
|
41
|
+
of an earlier job.
|
|
42
|
+
- Try to set a dynamic environment URL using `needs.deploy.outputs.url`.
|
|
43
|
+
|
|
44
|
+
The error appears only at workflow validation time (not at job runtime), which means
|
|
45
|
+
the entire workflow is rejected before any jobs run.
|
|
46
|
+
fix: |
|
|
47
|
+
Use the explicit `environment:` mapping format with `name:` and optionally `url:` sub-keys
|
|
48
|
+
instead of the shorthand string with a `${{ }}` expression.
|
|
49
|
+
|
|
50
|
+
The shorthand `environment: ${{ expression }}` is only for literal strings or simple
|
|
51
|
+
expressions without `needs.*` / `jobs.*`. Once you need cross-job outputs, switch to
|
|
52
|
+
the explicit form.
|
|
53
|
+
fix_code:
|
|
54
|
+
- language: yaml
|
|
55
|
+
label: 'Broken — shorthand with needs.* context causes TemplateValidationException'
|
|
56
|
+
code: |
|
|
57
|
+
jobs:
|
|
58
|
+
determine-env:
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
outputs:
|
|
61
|
+
target: ${{ steps.pick.outputs.env }}
|
|
62
|
+
steps:
|
|
63
|
+
- id: pick
|
|
64
|
+
run: echo "env=production" >> $GITHUB_OUTPUT
|
|
65
|
+
|
|
66
|
+
deploy:
|
|
67
|
+
needs: determine-env
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
# ❌ BROKEN: shorthand does not support needs.* context
|
|
70
|
+
environment: ${{ needs.determine-env.outputs.target }}
|
|
71
|
+
steps:
|
|
72
|
+
- run: echo "deploying"
|
|
73
|
+
|
|
74
|
+
- language: yaml
|
|
75
|
+
label: 'Fixed — explicit environment.name form supports needs.* context'
|
|
76
|
+
code: |
|
|
77
|
+
jobs:
|
|
78
|
+
determine-env:
|
|
79
|
+
runs-on: ubuntu-latest
|
|
80
|
+
outputs:
|
|
81
|
+
target: ${{ steps.pick.outputs.env }}
|
|
82
|
+
steps:
|
|
83
|
+
- id: pick
|
|
84
|
+
run: |
|
|
85
|
+
if [[ "${{ github.ref_name }}" == "main" ]]; then
|
|
86
|
+
echo "env=production" >> $GITHUB_OUTPUT
|
|
87
|
+
else
|
|
88
|
+
echo "env=staging" >> $GITHUB_OUTPUT
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
deploy:
|
|
92
|
+
needs: determine-env
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
# ✅ FIXED: explicit name: sub-key supports needs.* context
|
|
95
|
+
environment:
|
|
96
|
+
name: ${{ needs.determine-env.outputs.target }}
|
|
97
|
+
url: ${{ needs.deploy.outputs.deploy_url }}
|
|
98
|
+
steps:
|
|
99
|
+
- run: echo "deploying to ${{ needs.determine-env.outputs.target }}"
|
|
100
|
+
|
|
101
|
+
- language: yaml
|
|
102
|
+
label: 'Dynamic environment from workflow_dispatch input — correct form'
|
|
103
|
+
code: |
|
|
104
|
+
on:
|
|
105
|
+
workflow_dispatch:
|
|
106
|
+
inputs:
|
|
107
|
+
environment:
|
|
108
|
+
required: true
|
|
109
|
+
type: environment
|
|
110
|
+
jobs:
|
|
111
|
+
deploy:
|
|
112
|
+
runs-on: ubuntu-latest
|
|
113
|
+
# ✅ Use explicit name: when referencing inputs.* or needs.* contexts
|
|
114
|
+
environment:
|
|
115
|
+
name: ${{ inputs.environment }}
|
|
116
|
+
steps:
|
|
117
|
+
- run: echo "deploying to ${{ inputs.environment }}"
|
|
118
|
+
prevention:
|
|
119
|
+
- 'Always use the `environment:\n name: ${{ }}` form (never the shorthand string) when the environment name is dynamic or derived from another job.'
|
|
120
|
+
- 'Remember: `environment: ${{ expression }}` is valid syntax but only for literal strings — it silently ignores `needs.*` context at parse time, producing a hard error.'
|
|
121
|
+
- 'Lint with actionlint (v1.7.0+) which detects this pattern before pushing.'
|
|
122
|
+
docs:
|
|
123
|
+
- url: 'https://github.com/actions/runner/issues/998'
|
|
124
|
+
label: 'actions/runner#998 — Setting job environment dynamically fails with needs context in shorthand form'
|
|
125
|
+
- url: 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idenvironment'
|
|
126
|
+
label: 'GitHub Docs — jobs.<job_id>.environment syntax reference'
|
|
127
|
+
- url: 'https://stackoverflow.com/questions/65826284/use-dynamic-input-value-for-environment-in-github-actions-workflow-job'
|
|
128
|
+
label: 'Stack Overflow — Dynamic environment in GitHub Actions workflow job (many upvotes)'
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
id: yaml-syntax-076
|
|
2
|
+
title: 'actionlint <=1.7.7 rejects valid `entrypoint` and `command` keys on service containers — schema lag after April 2026 GitHub changelog'
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- actionlint
|
|
7
|
+
- service-container
|
|
8
|
+
- entrypoint
|
|
9
|
+
- command
|
|
10
|
+
- schema-lag
|
|
11
|
+
- linting
|
|
12
|
+
- CI-check
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'unexpected key "entrypoint" for "services" section'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'unexpected key "command" for "services" section'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
- regex: 'unexpected key "(?:entrypoint|command)" for "services" section\. expected one of "credentials", "env", "image", "options", "ports", "volumes"'
|
|
19
|
+
flags: 'i'
|
|
20
|
+
error_messages:
|
|
21
|
+
- '.github/workflows/test.yaml:9:9: unexpected key "entrypoint" for "services" section. expected one of "credentials", "env", "image", "options", "ports", "volumes" [syntax-check]'
|
|
22
|
+
- '.github/workflows/test.yaml:10:9: unexpected key "command" for "services" section. expected one of "credentials", "env", "image", "options", "ports", "volumes" [syntax-check]'
|
|
23
|
+
root_cause: |
|
|
24
|
+
GitHub Actions added support for `entrypoint` and `command` keys on service container
|
|
25
|
+
definitions on April 2, 2026 (GitHub Actions: Early April 2026 updates changelog). These
|
|
26
|
+
new keys allow overriding the service container's default entrypoint and command directly from
|
|
27
|
+
workflow YAML, matching Docker Compose semantics:
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
services:
|
|
31
|
+
redis:
|
|
32
|
+
image: redis
|
|
33
|
+
entrypoint: redis-server
|
|
34
|
+
command: --save 60 1 --loglevel warning
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
actionlint versions <=1.7.7 validate service container keys against a hardcoded schema that
|
|
38
|
+
only permits `credentials`, `env`, `image`, `options`, `ports`, and `volumes`. When a workflow
|
|
39
|
+
uses the new `entrypoint` or `command` keys, actionlint reports them as unknown properties with
|
|
40
|
+
a `[syntax-check]` error.
|
|
41
|
+
|
|
42
|
+
This causes CI pipelines that run actionlint as a lint check to fail even though the workflow
|
|
43
|
+
is perfectly valid on GitHub.com. The fix was merged into actionlint on April 19, 2026 and
|
|
44
|
+
released in v1.7.8.
|
|
45
|
+
|
|
46
|
+
This is a schema-lag pattern: a new GitHub Actions feature was released before the static
|
|
47
|
+
analysis tool's schema was updated to recognize it, causing a false-positive lint failure.
|
|
48
|
+
fix: |
|
|
49
|
+
**Primary fix: Upgrade actionlint to >=1.7.8.**
|
|
50
|
+
The fix was merged April 19, 2026 (rhysd/actionlint#644). Update your CI pipeline to use
|
|
51
|
+
actionlint >=1.7.8:
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
- name: Run actionlint
|
|
55
|
+
uses: raven-actions/actionlint@v2
|
|
56
|
+
with:
|
|
57
|
+
version: latest # or pin to 1.7.8+
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Temporary workaround (if upgrading is not immediately possible):** Add an inline ignore
|
|
61
|
+
comment to suppress the false-positive while on the older actionlint version:
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
services:
|
|
65
|
+
redis:
|
|
66
|
+
image: redis
|
|
67
|
+
entrypoint: redis-server # actionlint:ignore
|
|
68
|
+
command: --save 60 1 # actionlint:ignore
|
|
69
|
+
```
|
|
70
|
+
fix_code:
|
|
71
|
+
- language: yaml
|
|
72
|
+
label: 'Upgrade actionlint in CI to >=1.7.8 to recognize entrypoint/command service keys'
|
|
73
|
+
code: |
|
|
74
|
+
- name: Run actionlint
|
|
75
|
+
uses: raven-actions/actionlint@v2
|
|
76
|
+
with:
|
|
77
|
+
version: 1.7.8 # Minimum version that supports entrypoint/command on service containers
|
|
78
|
+
- language: yaml
|
|
79
|
+
label: 'Valid service container workflow using new entrypoint/command keys (GitHub Actions only)'
|
|
80
|
+
code: |
|
|
81
|
+
jobs:
|
|
82
|
+
test:
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
services:
|
|
85
|
+
redis:
|
|
86
|
+
image: redis:7
|
|
87
|
+
# These keys are valid on GitHub Actions (GA April 2, 2026)
|
|
88
|
+
# but require actionlint >=1.7.8 for linting to pass
|
|
89
|
+
entrypoint: redis-server
|
|
90
|
+
command: --save 60 1 --loglevel warning
|
|
91
|
+
ports:
|
|
92
|
+
- 6379:6379
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@v4
|
|
95
|
+
- name: Run tests
|
|
96
|
+
run: npm test
|
|
97
|
+
prevention:
|
|
98
|
+
- 'Pin actionlint to a specific version in CI and include it in your Dependabot or Renovate configuration so schema updates are picked up promptly when new GitHub Actions features are released.'
|
|
99
|
+
- 'When a new GitHub Actions feature ships, check the actionlint changelog (https://github.com/rhysd/actionlint/releases) for schema support before using the feature in linted workflows.'
|
|
100
|
+
- 'Use `# actionlint:ignore` comment as a short-term workaround for valid-but-unknown-to-linter keys while waiting for the schema update.'
|
|
101
|
+
docs:
|
|
102
|
+
- url: 'https://github.com/rhysd/actionlint/issues/644'
|
|
103
|
+
label: 'rhysd/actionlint#644 — Add support for entrypoint and command for service containers (fixed in v1.7.8)'
|
|
104
|
+
- url: 'https://github.blog/changelog/2026-04-02-github-actions-early-april-2026-updates/'
|
|
105
|
+
label: 'GitHub Changelog — April 2026 updates: entrypoint and command overrides for service containers'
|
|
106
|
+
- url: 'https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idservicesservice_identrypoint'
|
|
107
|
+
label: 'GitHub Docs — Workflow syntax: jobs.<job_id>.services.<service_id>.entrypoint'
|
package/package.json
CHANGED