@htekdev/actions-debugger 1.0.54 → 1.0.56
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/known-unsolved/known-unsolved-037.yml +124 -0
- package/errors/permissions-auth/permissions-auth-040.yml +142 -0
- package/errors/silent-failures/silent-failures-055.yml +99 -0
- package/errors/silent-failures/silent-failures-056.yml +105 -0
- package/errors/triggers/triggers-039.yml +115 -0
- package/errors/triggers/triggers-040.yml +104 -0
- package/errors/yaml-syntax/yaml-syntax-038.yml +91 -0
- package/errors/yaml-syntax/yaml-syntax-039.yml +115 -0
- package/package.json +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
id: known-unsolved-037
|
|
2
|
+
title: "Skipped jobs show as 'skipped' — branch protection required status checks block PR merge"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- required-status-checks
|
|
7
|
+
- branch-protection
|
|
8
|
+
- skipped
|
|
9
|
+
- conditional-job
|
|
10
|
+
- merge-blocked
|
|
11
|
+
- known-limitation
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'if:\s+github\.event_name\s+!=\s+'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'if:\s+\$\{\{\s*github\.event_name\s*!=\s*'
|
|
16
|
+
flags: i
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Required status check 'ci/test' was not successful — status: skipped"
|
|
19
|
+
- "Branch protection rule requires passing status checks before merging"
|
|
20
|
+
root_cause: |
|
|
21
|
+
When a job's if: condition evaluates to false, GitHub Actions marks that job as
|
|
22
|
+
"Skipped" in the Checks API. Branch protection rules that require a specific check
|
|
23
|
+
to pass only accept a conclusion of "success" — not "skipped", "cancelled", or
|
|
24
|
+
"neutral".
|
|
25
|
+
|
|
26
|
+
This creates a practical trap: a team sets up a required check for a job like
|
|
27
|
+
ci/test, then later adds an if: condition to skip that job on certain events
|
|
28
|
+
(for example, to avoid running tests on tag pushes or documentation-only changes).
|
|
29
|
+
The conditional logic is correct for workflow execution, but the skipped conclusion
|
|
30
|
+
permanently blocks all PRs where that condition evaluates to false.
|
|
31
|
+
|
|
32
|
+
GitHub has documented this as intentional: a skipped job is not a successful job.
|
|
33
|
+
There is no configuration option to treat "skipped" as equivalent to "success" for
|
|
34
|
+
branch protection purposes as of 2026. The workaround requires a structural change
|
|
35
|
+
to the workflow.
|
|
36
|
+
fix: |
|
|
37
|
+
This is a known limitation with no direct fix. The recommended workarounds are:
|
|
38
|
+
|
|
39
|
+
1. ALWAYS-PASS WRAPPER JOB: Create a wrapper job that always runs and reports
|
|
40
|
+
the real outcome. The wrapper job becomes the required status check instead of
|
|
41
|
+
the real CI job. If CI was skipped (e.g., docs-only change), the wrapper passes.
|
|
42
|
+
If CI ran and failed, the wrapper fails.
|
|
43
|
+
|
|
44
|
+
2. MOVE THE CONDITION INSIDE THE JOB: Instead of using if: at the job level, keep
|
|
45
|
+
the job always running and use if: at the step level. An empty job always
|
|
46
|
+
succeeds, so the required status check passes.
|
|
47
|
+
|
|
48
|
+
3. PATHS FILTER PATTERN (dorny/paths-filter): Use a separate filtering job and
|
|
49
|
+
pass its output as a condition parameter to downstream jobs while keeping a
|
|
50
|
+
pass-through job for status check reporting.
|
|
51
|
+
|
|
52
|
+
4. RE-EVALUATE THE REQUIRED CHECK: If the check is truly optional for some events,
|
|
53
|
+
consider removing it from required status checks and instead enforce it only at
|
|
54
|
+
the merge queue level.
|
|
55
|
+
fix_code:
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: "Problem: conditional job is skipped, blocking required status check"
|
|
58
|
+
code: |
|
|
59
|
+
on: [push, pull_request]
|
|
60
|
+
|
|
61
|
+
jobs:
|
|
62
|
+
test:
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
# This job is required in branch protection, but gets skipped on push to main
|
|
65
|
+
if: github.event_name == 'pull_request'
|
|
66
|
+
steps:
|
|
67
|
+
- run: npm test
|
|
68
|
+
# On push events, this job is SKIPPED, blocking required status check
|
|
69
|
+
- language: yaml
|
|
70
|
+
label: "Fix: always-pass wrapper job becomes the required status check"
|
|
71
|
+
code: |
|
|
72
|
+
on: [push, pull_request]
|
|
73
|
+
|
|
74
|
+
jobs:
|
|
75
|
+
test:
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
if: github.event_name == 'pull_request'
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/checkout@v4
|
|
80
|
+
- run: npm test
|
|
81
|
+
|
|
82
|
+
# Set THIS job as the required status check in branch protection
|
|
83
|
+
ci-status:
|
|
84
|
+
runs-on: ubuntu-latest
|
|
85
|
+
needs: [test]
|
|
86
|
+
if: always()
|
|
87
|
+
steps:
|
|
88
|
+
- name: Report CI result
|
|
89
|
+
run: |
|
|
90
|
+
result="${{ needs.test.result }}"
|
|
91
|
+
if [[ "$result" == "success" || "$result" == "skipped" ]]; then
|
|
92
|
+
echo "CI passed or was intentionally skipped — OK to merge"
|
|
93
|
+
else
|
|
94
|
+
echo "CI failed (result: $result)"
|
|
95
|
+
exit 1
|
|
96
|
+
fi
|
|
97
|
+
- language: yaml
|
|
98
|
+
label: "Alternative: move condition inside job so job always runs and passes when empty"
|
|
99
|
+
code: |
|
|
100
|
+
on: [push, pull_request]
|
|
101
|
+
|
|
102
|
+
jobs:
|
|
103
|
+
test:
|
|
104
|
+
runs-on: ubuntu-latest
|
|
105
|
+
# No if: at job level — job always runs and always produces a conclusion
|
|
106
|
+
steps:
|
|
107
|
+
- uses: actions/checkout@v4
|
|
108
|
+
|
|
109
|
+
- name: Run tests (pull_request only)
|
|
110
|
+
if: github.event_name == 'pull_request'
|
|
111
|
+
run: npm test
|
|
112
|
+
# If step is skipped, job still succeeds — required check passes
|
|
113
|
+
prevention:
|
|
114
|
+
- "Never add an if: condition to a job that is a required status check — use a wrapper job instead"
|
|
115
|
+
- "Use the always-pass wrapper pattern from the start when adding new required status checks"
|
|
116
|
+
- "Test required check behavior by creating a draft PR that intentionally triggers the skip condition"
|
|
117
|
+
- "Document which jobs are required status checks in a comment at the top of the workflow file"
|
|
118
|
+
docs:
|
|
119
|
+
- url: "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/troubleshooting-required-status-checks"
|
|
120
|
+
label: "GitHub Docs: Troubleshooting required status checks"
|
|
121
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idif"
|
|
122
|
+
label: "GitHub Docs: Job-level if conditions"
|
|
123
|
+
- url: "https://github.com/dorny/paths-filter"
|
|
124
|
+
label: "dorny/paths-filter: Conditional execution with status reporting"
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
id: permissions-auth-040
|
|
2
|
+
title: "GITHUB_TOKEN is read-only by default in new repositories — write operations fail with 403"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- GITHUB_TOKEN
|
|
7
|
+
- read-only
|
|
8
|
+
- permissions
|
|
9
|
+
- 403
|
|
10
|
+
- new-repo
|
|
11
|
+
- workflow-permissions
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'remote:\s*Permission to .* denied to github-actions\[bot\]'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'Error:\s*Resource not accessible by integration'
|
|
16
|
+
flags: i
|
|
17
|
+
- regex: 'HttpError:\s*Resource not accessible by integration'
|
|
18
|
+
flags: i
|
|
19
|
+
- regex: '403.*github-actions\[bot\]'
|
|
20
|
+
flags: i
|
|
21
|
+
error_messages:
|
|
22
|
+
- "remote: Permission to org/repo.git denied to github-actions[bot]"
|
|
23
|
+
- "Error: Resource not accessible by integration"
|
|
24
|
+
- "HttpError: Resource not accessible by integration"
|
|
25
|
+
- "fatal: unable to access 'https://github.com/org/repo/': The requested URL returned error: 403"
|
|
26
|
+
- "GitHub Actions is not permitted to create or approve pull requests"
|
|
27
|
+
root_cause: |
|
|
28
|
+
In February 2023 (GitHub Changelog), GitHub changed the default GITHUB_TOKEN
|
|
29
|
+
permissions for all new repositories and new organizations from read-write to
|
|
30
|
+
read-only. Repositories created before this change retain their original default
|
|
31
|
+
(read-write) unless an administrator explicitly changes the org-level setting.
|
|
32
|
+
|
|
33
|
+
This creates two failure scenarios:
|
|
34
|
+
|
|
35
|
+
1. COPIED WORKFLOWS: A developer copies a workflow from an older repo or a public
|
|
36
|
+
template that relies on the default read-write GITHUB_TOKEN (for example, to push
|
|
37
|
+
a build artifact, create a release, comment on a PR, or open a pull request). In a
|
|
38
|
+
new repo, that same workflow fails with 403 or "Resource not accessible by
|
|
39
|
+
integration" because the token only has read permissions.
|
|
40
|
+
|
|
41
|
+
2. ORG POLICY CHANGE: An organization administrator enables the read-only default
|
|
42
|
+
at the organization level (Settings > Actions > General > Workflow permissions).
|
|
43
|
+
All repos in the org immediately start failing any workflow that performs write
|
|
44
|
+
operations without an explicit permissions block.
|
|
45
|
+
|
|
46
|
+
The failure message is typically cryptic and does not mention that the root cause is
|
|
47
|
+
the workflow permissions default — it only says the token was denied.
|
|
48
|
+
fix: |
|
|
49
|
+
Add explicit permissions blocks to workflows or jobs that perform write operations.
|
|
50
|
+
GitHub recommends the principle of least privilege: grant only the specific write
|
|
51
|
+
permission needed, not blanket read-write access.
|
|
52
|
+
|
|
53
|
+
At the workflow level, a top-level permissions: block sets defaults for all jobs.
|
|
54
|
+
At the job level, a permissions: block overrides the workflow-level default for that
|
|
55
|
+
job only. Job-level is preferred — it minimises the blast radius if a job is
|
|
56
|
+
compromised.
|
|
57
|
+
|
|
58
|
+
Common permissions needed:
|
|
59
|
+
- contents: write — push commits, create releases, upload release assets
|
|
60
|
+
- pull-requests: write — create/comment on pull requests
|
|
61
|
+
- issues: write — create/comment on issues
|
|
62
|
+
- packages: write — publish to GitHub Packages
|
|
63
|
+
- pages: write — deploy to GitHub Pages (also needs id-token: write for OIDC)
|
|
64
|
+
|
|
65
|
+
Alternatively, change the default at repo level: Settings > Actions > General >
|
|
66
|
+
Workflow permissions > Read and write permissions. Not recommended for security-
|
|
67
|
+
conscious teams.
|
|
68
|
+
fix_code:
|
|
69
|
+
- language: yaml
|
|
70
|
+
label: "Problem: workflow relies on default write token, fails in new repos"
|
|
71
|
+
code: |
|
|
72
|
+
# No permissions block — relies on default, which is READ-ONLY in new repos
|
|
73
|
+
on: push
|
|
74
|
+
|
|
75
|
+
jobs:
|
|
76
|
+
release:
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/checkout@v4
|
|
80
|
+
- name: Build
|
|
81
|
+
run: npm run build
|
|
82
|
+
- name: Create GitHub Release
|
|
83
|
+
uses: softprops/action-gh-release@v2
|
|
84
|
+
with:
|
|
85
|
+
files: dist/**
|
|
86
|
+
# Fails with 403 in new repos — needs contents:write
|
|
87
|
+
- language: yaml
|
|
88
|
+
label: "Fix: explicit job-level permissions (least privilege)"
|
|
89
|
+
code: |
|
|
90
|
+
on: push
|
|
91
|
+
|
|
92
|
+
jobs:
|
|
93
|
+
release:
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
permissions:
|
|
96
|
+
contents: write # Required to create releases and upload assets
|
|
97
|
+
steps:
|
|
98
|
+
- uses: actions/checkout@v4
|
|
99
|
+
- name: Build
|
|
100
|
+
run: npm run build
|
|
101
|
+
- name: Create GitHub Release
|
|
102
|
+
uses: softprops/action-gh-release@v2
|
|
103
|
+
with:
|
|
104
|
+
files: dist/**
|
|
105
|
+
- language: yaml
|
|
106
|
+
label: "Workflow-level permissions (when multiple jobs all need write access)"
|
|
107
|
+
code: |
|
|
108
|
+
on: push
|
|
109
|
+
|
|
110
|
+
# Set workflow-level default — overridden per job as needed
|
|
111
|
+
permissions:
|
|
112
|
+
contents: write
|
|
113
|
+
pull-requests: write
|
|
114
|
+
|
|
115
|
+
jobs:
|
|
116
|
+
build-and-release:
|
|
117
|
+
runs-on: ubuntu-latest
|
|
118
|
+
# Inherits workflow-level permissions
|
|
119
|
+
steps:
|
|
120
|
+
- uses: actions/checkout@v4
|
|
121
|
+
- run: npm run build
|
|
122
|
+
|
|
123
|
+
comment-on-pr:
|
|
124
|
+
runs-on: ubuntu-latest
|
|
125
|
+
permissions:
|
|
126
|
+
pull-requests: write # Job-level — more restrictive than workflow default
|
|
127
|
+
contents: read
|
|
128
|
+
steps:
|
|
129
|
+
- uses: actions/checkout@v4
|
|
130
|
+
prevention:
|
|
131
|
+
- "Always declare explicit permissions blocks in workflows that push, create releases, comment, or deploy — never rely on defaults"
|
|
132
|
+
- "Run 'actionlint' locally — it warns when a workflow uses write-requiring actions without corresponding permissions"
|
|
133
|
+
- "Use the minimum permissions required: grant 'contents: write' only on jobs that need it, not at workflow level"
|
|
134
|
+
- "When copying a workflow from another repo, check whether it has a permissions block — add one if missing"
|
|
135
|
+
- "Review the GitHub Actions audit log when a workflow fails with 403 to confirm the token permissions in use"
|
|
136
|
+
docs:
|
|
137
|
+
- url: "https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token"
|
|
138
|
+
label: "GitHub Docs: GITHUB_TOKEN permissions"
|
|
139
|
+
- url: "https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#setting-the-permissions-of-the-github_token-for-your-repository"
|
|
140
|
+
label: "GitHub Docs: Setting default workflow permissions"
|
|
141
|
+
- url: "https://github.blog/changelog/2023-02-02-github-actions-updating-the-default-github_token-permissions-to-read-only/"
|
|
142
|
+
label: "GitHub Changelog: Default GITHUB_TOKEN permissions changed to read-only (Feb 2023)"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
id: silent-failures-055
|
|
2
|
+
title: "'github.event_name' is 'workflow_call' inside reusable workflows — not the caller's event"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- reusable-workflow
|
|
7
|
+
- workflow_call
|
|
8
|
+
- event_name
|
|
9
|
+
- github-context
|
|
10
|
+
- conditional
|
|
11
|
+
- silent
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'github\.event_name\s*==\s*[''"]push[''"]'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'github\.event_name\s*==\s*[''"]pull_request[''"]'
|
|
16
|
+
flags: i
|
|
17
|
+
- regex: 'github\.event_name\s*==\s*[''"]workflow_dispatch[''"]'
|
|
18
|
+
flags: i
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Evaluating: github.event_name == 'push' => false"
|
|
21
|
+
- "Step skipped: if condition 'github.event_name == \"push\"' was false"
|
|
22
|
+
root_cause: |
|
|
23
|
+
When a workflow is called as a reusable workflow via on: workflow_call, GitHub Actions
|
|
24
|
+
sets github.event_name to "workflow_call" inside that workflow — NOT to the triggering
|
|
25
|
+
event from the caller workflow.
|
|
26
|
+
|
|
27
|
+
A common mistake is to write conditional logic in a reusable workflow that checks
|
|
28
|
+
github.event_name expecting the caller's event (push, pull_request, workflow_dispatch,
|
|
29
|
+
etc.). These conditions always evaluate to false when the workflow is invoked via
|
|
30
|
+
workflow_call, silently skipping the affected steps or entire jobs.
|
|
31
|
+
|
|
32
|
+
The behaviour is especially confusing because developers often test the reusable workflow
|
|
33
|
+
in isolation via workflow_dispatch — in which case github.event_name IS correct. The
|
|
34
|
+
silent skip only appears when the workflow is called from another workflow, and the skipped
|
|
35
|
+
step or job disappears from the run summary with no error message.
|
|
36
|
+
fix: |
|
|
37
|
+
Pass the caller's event name as an explicit input to the reusable workflow. The caller
|
|
38
|
+
reads github.event_name from its own context (where it is correct) and passes it as a
|
|
39
|
+
string input. Inside the reusable workflow, check inputs.caller_event (or whatever name
|
|
40
|
+
you choose) instead of github.event_name.
|
|
41
|
+
|
|
42
|
+
Alternatively, restructure so that event-specific logic stays in the caller workflow and
|
|
43
|
+
the reusable workflow only receives final parameters — not the raw event name.
|
|
44
|
+
fix_code:
|
|
45
|
+
- language: yaml
|
|
46
|
+
label: "Problem: event_name check always false in reusable workflow"
|
|
47
|
+
code: |
|
|
48
|
+
# reusable.yml — WRONG: event_name is always "workflow_call" here
|
|
49
|
+
on:
|
|
50
|
+
workflow_call:
|
|
51
|
+
|
|
52
|
+
jobs:
|
|
53
|
+
build:
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- name: Deploy only on push
|
|
57
|
+
if: github.event_name == 'push' # Always false — event_name is "workflow_call"
|
|
58
|
+
run: echo "Deploying..."
|
|
59
|
+
- language: yaml
|
|
60
|
+
label: "Fix: accept caller_event as an explicit input"
|
|
61
|
+
code: |
|
|
62
|
+
# reusable.yml — CORRECT: accept the caller's event as an input
|
|
63
|
+
on:
|
|
64
|
+
workflow_call:
|
|
65
|
+
inputs:
|
|
66
|
+
caller_event:
|
|
67
|
+
type: string
|
|
68
|
+
required: true
|
|
69
|
+
|
|
70
|
+
jobs:
|
|
71
|
+
build:
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- name: Deploy only on push
|
|
75
|
+
if: inputs.caller_event == 'push' # Correct — uses the passed-in value
|
|
76
|
+
run: echo "Deploying..."
|
|
77
|
+
- language: yaml
|
|
78
|
+
label: "Caller workflow: pass github.event_name as input"
|
|
79
|
+
code: |
|
|
80
|
+
# caller.yml — pass the caller's event name to the reusable workflow
|
|
81
|
+
on: [push, pull_request]
|
|
82
|
+
|
|
83
|
+
jobs:
|
|
84
|
+
call-reusable:
|
|
85
|
+
uses: ./.github/workflows/reusable.yml
|
|
86
|
+
with:
|
|
87
|
+
caller_event: ${{ github.event_name }} # "push" or "pull_request"
|
|
88
|
+
prevention:
|
|
89
|
+
- "Never use 'github.event_name' in a reusable workflow to detect the caller's event — it will always be 'workflow_call'"
|
|
90
|
+
- "Pass event-specific data as explicit inputs; document expected values in the input description field"
|
|
91
|
+
- "When testing a reusable workflow via workflow_dispatch, simulate the production caller_event value via inputs to catch conditional errors early"
|
|
92
|
+
- "The same rule applies to github.event.* properties — many will be empty or refer to the workflow_call event, not the caller's event"
|
|
93
|
+
docs:
|
|
94
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows"
|
|
95
|
+
label: "GitHub Docs: Reusing workflows"
|
|
96
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#github-context"
|
|
97
|
+
label: "GitHub Docs: github context reference"
|
|
98
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows#limitations"
|
|
99
|
+
label: "GitHub Docs: Reusable workflow limitations"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
id: silent-failures-056
|
|
2
|
+
title: "'continue-on-error: true' makes a failed job report success in required status checks"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- continue-on-error
|
|
7
|
+
- required-status-checks
|
|
8
|
+
- branch-protection
|
|
9
|
+
- checks-api
|
|
10
|
+
- silent
|
|
11
|
+
- branch-protection-bypass
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'continue-on-error:\s*true'
|
|
14
|
+
flags: i
|
|
15
|
+
error_messages:
|
|
16
|
+
- "Job succeeded (continue-on-error)"
|
|
17
|
+
- "Required status check passed (job actually failed but continue-on-error: true)"
|
|
18
|
+
root_cause: |
|
|
19
|
+
When a job has continue-on-error: true set, GitHub Actions converts the job's conclusion
|
|
20
|
+
from failure to success before writing the result to the Checks API. The branch
|
|
21
|
+
protection system only sees the API-level conclusion — which is "success" — and allows
|
|
22
|
+
the pull request to merge.
|
|
23
|
+
|
|
24
|
+
The job timeline in the GitHub UI shows a yellow icon with "(Cancelled)" or similar
|
|
25
|
+
wording, but the Checks API and branch protection treat it as passed. Developers
|
|
26
|
+
relying on required status checks to enforce quality gates unknowingly lose that
|
|
27
|
+
protection for any job marked continue-on-error: true.
|
|
28
|
+
|
|
29
|
+
This is particularly dangerous on jobs that run security scans, license checks, or
|
|
30
|
+
integration tests where the team deliberately added them as required checks to block
|
|
31
|
+
broken or risky changes from merging.
|
|
32
|
+
fix: |
|
|
33
|
+
Avoid using continue-on-error: true on jobs that are configured as required status
|
|
34
|
+
checks in branch protection rules. Instead, handle acceptable failure conditions
|
|
35
|
+
explicitly inside the job using if: steps.*.outcome == 'failure' logic.
|
|
36
|
+
|
|
37
|
+
If you need to always continue a workflow past a flaky or optional step but still
|
|
38
|
+
surface failures in status checks, use a separate sentinel job that:
|
|
39
|
+
1. Depends on the real job with needs:
|
|
40
|
+
2. Runs with if: always()
|
|
41
|
+
3. Fails explicitly if the upstream job failed
|
|
42
|
+
|
|
43
|
+
This pattern separates "keep the workflow running" from "record the real outcome".
|
|
44
|
+
fix_code:
|
|
45
|
+
- language: yaml
|
|
46
|
+
label: "Problem: continue-on-error silently hides failures from status checks"
|
|
47
|
+
code: |
|
|
48
|
+
jobs:
|
|
49
|
+
security-scan:
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
continue-on-error: true # DANGER: required status check will PASS even if scan fails
|
|
52
|
+
steps:
|
|
53
|
+
- name: Run security scan
|
|
54
|
+
run: ./run-scan.sh
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: "Fix: use a sentinel job to preserve the real outcome"
|
|
57
|
+
code: |
|
|
58
|
+
jobs:
|
|
59
|
+
security-scan:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
# No continue-on-error here
|
|
62
|
+
steps:
|
|
63
|
+
- name: Run security scan
|
|
64
|
+
run: ./run-scan.sh
|
|
65
|
+
|
|
66
|
+
# This is the job to set as the required status check
|
|
67
|
+
security-gate:
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
needs: [security-scan]
|
|
70
|
+
if: always()
|
|
71
|
+
steps:
|
|
72
|
+
- name: Check security scan result
|
|
73
|
+
if: needs.security-scan.result != 'success'
|
|
74
|
+
run: |
|
|
75
|
+
echo "Security scan did not pass (result: ${{ needs.security-scan.result }})"
|
|
76
|
+
exit 1
|
|
77
|
+
- language: yaml
|
|
78
|
+
label: "Alternative: handle acceptable failures inside the step, not the job"
|
|
79
|
+
code: |
|
|
80
|
+
jobs:
|
|
81
|
+
test:
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
steps:
|
|
84
|
+
- name: Run optional extended tests
|
|
85
|
+
id: extended
|
|
86
|
+
run: ./run-extended-tests.sh
|
|
87
|
+
continue-on-error: true # Step-level is safer than job-level
|
|
88
|
+
|
|
89
|
+
- name: Fail job if extended tests failed unexpectedly
|
|
90
|
+
if: steps.extended.outcome == 'failure'
|
|
91
|
+
run: |
|
|
92
|
+
echo "Extended tests failed — blocking merge"
|
|
93
|
+
exit 1
|
|
94
|
+
prevention:
|
|
95
|
+
- "Never set continue-on-error: true at the job level for jobs that are required status checks"
|
|
96
|
+
- "Use continue-on-error: true at the step level instead — it does not affect the Checks API job conclusion"
|
|
97
|
+
- "Audit your branch protection required checks against all jobs that have continue-on-error: true"
|
|
98
|
+
- "Use the sentinel job pattern to separate workflow continuation from status reporting"
|
|
99
|
+
docs:
|
|
100
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idcontinue-on-error"
|
|
101
|
+
label: "GitHub Docs: continue-on-error syntax"
|
|
102
|
+
- url: "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging"
|
|
103
|
+
label: "GitHub Docs: Required status checks before merging"
|
|
104
|
+
- url: "https://docs.github.com/en/rest/checks/runs"
|
|
105
|
+
label: "GitHub REST API: Checks runs"
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
id: triggers-039
|
|
2
|
+
title: "'github.sha' in pull_request_target points to base branch HEAD, not the PR's head commit"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- pull_request_target
|
|
7
|
+
- github-sha
|
|
8
|
+
- base-branch
|
|
9
|
+
- pr-head
|
|
10
|
+
- checkout
|
|
11
|
+
- security
|
|
12
|
+
- silent
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'pull_request_target'
|
|
15
|
+
flags: i
|
|
16
|
+
- regex: 'github\.event\.pull_request\.head\.sha'
|
|
17
|
+
flags: i
|
|
18
|
+
- regex: 'github\.sha.*pull_request_target'
|
|
19
|
+
flags: i
|
|
20
|
+
error_messages:
|
|
21
|
+
- "Tests passed but PR code was never executed (checked out base branch at github.sha)"
|
|
22
|
+
root_cause: |
|
|
23
|
+
The pull_request_target event fires in the context of the BASE branch of a pull request,
|
|
24
|
+
not the PR's head branch. Consequently, github.sha inside a pull_request_target workflow
|
|
25
|
+
is the HEAD commit SHA of the BASE branch — typically main or master — not the PR
|
|
26
|
+
contributor's changes.
|
|
27
|
+
|
|
28
|
+
This design is intentional: pull_request_target runs with repository secrets and write
|
|
29
|
+
permissions available, so GitHub restricts it to trusted base-branch code to prevent
|
|
30
|
+
malicious PR code from exfiltrating secrets. However, it causes a silent logic error when
|
|
31
|
+
developers expect github.sha (or the default actions/checkout behaviour, which checks out
|
|
32
|
+
github.sha) to build and test the PR's changes.
|
|
33
|
+
|
|
34
|
+
The result is a CI run that appears to succeed — but it compiled and tested the base
|
|
35
|
+
branch code, not the pull request's changes. The PR author sees green CI even though
|
|
36
|
+
their changes were never executed.
|
|
37
|
+
fix: |
|
|
38
|
+
To work with PR head code in a pull_request_target workflow, explicitly specify the PR
|
|
39
|
+
head SHA using github.event.pull_request.head.sha in the checkout step's ref: input.
|
|
40
|
+
|
|
41
|
+
SECURITY WARNING: checking out untrusted PR code in a workflow that has access to secrets
|
|
42
|
+
creates a pwn-request vulnerability. Only check out PR head code in jobs with minimal
|
|
43
|
+
permissions and no secret exposure.
|
|
44
|
+
|
|
45
|
+
For most CI use cases — building, linting, testing — use the standard pull_request event
|
|
46
|
+
instead of pull_request_target. The pull_request event correctly checks out PR changes
|
|
47
|
+
but runs without repository secrets, which is the safe and intended design.
|
|
48
|
+
fix_code:
|
|
49
|
+
- language: yaml
|
|
50
|
+
label: "Problem: default checkout in pull_request_target checks out base branch"
|
|
51
|
+
code: |
|
|
52
|
+
on: pull_request_target
|
|
53
|
+
|
|
54
|
+
jobs:
|
|
55
|
+
test:
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/checkout@v4
|
|
59
|
+
# No ref specified — uses github.sha which is BASE branch HEAD, not PR code
|
|
60
|
+
- run: npm test # Tests base branch, not the PR changes
|
|
61
|
+
- language: yaml
|
|
62
|
+
label: "Explicit PR head checkout (only when no secrets are exposed)"
|
|
63
|
+
code: |
|
|
64
|
+
on: pull_request_target
|
|
65
|
+
|
|
66
|
+
jobs:
|
|
67
|
+
label-and-check:
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
permissions:
|
|
70
|
+
pull-requests: write # Minimal permissions — no secrets
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@v4
|
|
73
|
+
with:
|
|
74
|
+
ref: ${{ github.event.pull_request.head.sha }}
|
|
75
|
+
# Only safe because this job has no secret access
|
|
76
|
+
# Do NOT add secrets: or environment: to jobs that do this
|
|
77
|
+
- language: yaml
|
|
78
|
+
label: "Recommended: use pull_request for testing, pull_request_target only for privileged operations"
|
|
79
|
+
code: |
|
|
80
|
+
# Standard PR testing — pull_request has no secrets but correctly checks out PR changes
|
|
81
|
+
on: pull_request
|
|
82
|
+
|
|
83
|
+
jobs:
|
|
84
|
+
test:
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/checkout@v4
|
|
88
|
+
# github.sha is the PR merge commit — correct for testing PR changes
|
|
89
|
+
- run: npm test
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
# Separate privileged workflow for labeling/commenting — uses pull_request_target
|
|
93
|
+
# This one works with base-branch context (github.sha = base HEAD) — which is correct
|
|
94
|
+
# for privileged operations that should only run trusted code
|
|
95
|
+
on: pull_request_target
|
|
96
|
+
|
|
97
|
+
jobs:
|
|
98
|
+
label:
|
|
99
|
+
runs-on: ubuntu-latest
|
|
100
|
+
permissions:
|
|
101
|
+
pull-requests: write
|
|
102
|
+
steps:
|
|
103
|
+
- uses: actions/labeler@v5
|
|
104
|
+
prevention:
|
|
105
|
+
- "Default to 'pull_request' event for building and testing PR code — it correctly checks out PR changes and has no secrets"
|
|
106
|
+
- "Use 'pull_request_target' only for privileged operations that genuinely need secrets or write permissions (labeling, commenting, deployment approval)"
|
|
107
|
+
- "Never expose repository secrets in the same job that checks out untrusted PR head code via github.event.pull_request.head.sha"
|
|
108
|
+
- "Add a comment next to any pull_request_target checkout step documenting which ref is used and why it is safe"
|
|
109
|
+
docs:
|
|
110
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_target"
|
|
111
|
+
label: "GitHub Docs: pull_request_target event"
|
|
112
|
+
- url: "https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/"
|
|
113
|
+
label: "GitHub Security Lab: Preventing pwn requests"
|
|
114
|
+
- url: "https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions"
|
|
115
|
+
label: "GitHub Docs: Security hardening for GitHub Actions"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
id: triggers-040
|
|
2
|
+
title: "Missing 'on: merge_group' trigger — required status checks fail in merge queue"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- merge-queue
|
|
7
|
+
- merge_group
|
|
8
|
+
- required-status-checks
|
|
9
|
+
- branch-protection
|
|
10
|
+
- ci-failure
|
|
11
|
+
- triggers
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'merge_group'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'Required status checks were not passing.*merge.queue'
|
|
16
|
+
flags: i
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Required status check 'ci/test' was not reported — PR ejected from merge queue"
|
|
19
|
+
- "This branch requires linear history. The merge queue could not merge this pull request."
|
|
20
|
+
root_cause: |
|
|
21
|
+
GitHub's merge queue (available since May 2023 GA) uses a dedicated event called
|
|
22
|
+
merge_group to trigger workflows. This is distinct from the pull_request event.
|
|
23
|
+
|
|
24
|
+
When a pull request enters the merge queue, GitHub creates a temporary merge branch
|
|
25
|
+
and fires the merge_group event. Workflows that only declare on: pull_request (or
|
|
26
|
+
other events) do not trigger on merge_group and therefore never create any status
|
|
27
|
+
check runs for the queued PR.
|
|
28
|
+
|
|
29
|
+
Branch protection rules that require specific status checks see no check result for
|
|
30
|
+
the merge group run and treat the absence as failure, ejecting the PR from the queue
|
|
31
|
+
with a message that the required checks were not passing.
|
|
32
|
+
|
|
33
|
+
The merge queue was added to GitHub Actions in May 2023 (GitHub Changelog). Existing
|
|
34
|
+
workflows created before that date have no merge_group trigger and must be updated
|
|
35
|
+
manually. Projects that enable merge queue on an existing repo will silently find
|
|
36
|
+
that all PRs are blocked from being merged via the queue.
|
|
37
|
+
fix: |
|
|
38
|
+
Add merge_group: to the on: trigger block alongside pull_request (and push, if used).
|
|
39
|
+
The merge_group event accepts no filter options — it fires whenever any PR enters
|
|
40
|
+
the merge queue for a protected branch.
|
|
41
|
+
|
|
42
|
+
If your workflow uses branch filters under on.pull_request.branches, those filters
|
|
43
|
+
do NOT apply to merge_group — the event always fires regardless of branch name.
|
|
44
|
+
This is intentional: the merge queue uses temporary synthetic branches.
|
|
45
|
+
fix_code:
|
|
46
|
+
- language: yaml
|
|
47
|
+
label: "Problem: workflow only triggers on pull_request, not in merge queue"
|
|
48
|
+
code: |
|
|
49
|
+
# BEFORE: only runs on pull_request — never fires in merge queue
|
|
50
|
+
on:
|
|
51
|
+
pull_request:
|
|
52
|
+
branches: [main]
|
|
53
|
+
|
|
54
|
+
jobs:
|
|
55
|
+
test:
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/checkout@v4
|
|
59
|
+
- run: npm test
|
|
60
|
+
- language: yaml
|
|
61
|
+
label: "Fix: add merge_group trigger so CI runs in the merge queue"
|
|
62
|
+
code: |
|
|
63
|
+
# AFTER: runs on both pull_request and when entering the merge queue
|
|
64
|
+
on:
|
|
65
|
+
pull_request:
|
|
66
|
+
branches: [main]
|
|
67
|
+
merge_group: # Required for merge queue — no filter options available
|
|
68
|
+
|
|
69
|
+
jobs:
|
|
70
|
+
test:
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@v4
|
|
74
|
+
- run: npm test
|
|
75
|
+
- language: yaml
|
|
76
|
+
label: "With push trigger (common three-trigger pattern)"
|
|
77
|
+
code: |
|
|
78
|
+
on:
|
|
79
|
+
push:
|
|
80
|
+
branches: [main]
|
|
81
|
+
pull_request:
|
|
82
|
+
branches: [main]
|
|
83
|
+
merge_group: # Always needed if merge queue is enabled on the repo
|
|
84
|
+
|
|
85
|
+
jobs:
|
|
86
|
+
ci:
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
steps:
|
|
89
|
+
- uses: actions/checkout@v4
|
|
90
|
+
- run: npm ci
|
|
91
|
+
- run: npm test
|
|
92
|
+
- run: npm run lint
|
|
93
|
+
prevention:
|
|
94
|
+
- "Any time you enable merge queue on a branch, audit all required-check workflows and add 'merge_group:' to each"
|
|
95
|
+
- "The merge_group event accepts no filter keys — do not try to add 'branches:' or 'types:' under it"
|
|
96
|
+
- "Test merge queue behavior by adding a PR to the queue and checking the Actions tab for the merge group run"
|
|
97
|
+
- "Add a comment near the merge_group trigger explaining its purpose for teammates unfamiliar with merge queues"
|
|
98
|
+
docs:
|
|
99
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#merge_group"
|
|
100
|
+
label: "GitHub Docs: merge_group event"
|
|
101
|
+
- url: "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue"
|
|
102
|
+
label: "GitHub Docs: Managing a merge queue"
|
|
103
|
+
- url: "https://github.blog/changelog/2023-05-12-github-actions-merge-queue-is-generally-available/"
|
|
104
|
+
label: "GitHub Changelog: Merge queue GA (May 2023)"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
id: yaml-syntax-038
|
|
2
|
+
title: "'::save-state::' and '::get-state::' workflow commands deprecated — upgrade to GITHUB_STATE"
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- save-state
|
|
7
|
+
- get-state
|
|
8
|
+
- deprecated
|
|
9
|
+
- workflow-commands
|
|
10
|
+
- environment-files
|
|
11
|
+
- GITHUB_STATE
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'The `save-state` command is deprecated'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'The `get-state` command is deprecated'
|
|
16
|
+
flags: i
|
|
17
|
+
- regex: '::save-state name='
|
|
18
|
+
flags: ''
|
|
19
|
+
- regex: '::get-state name='
|
|
20
|
+
flags: ''
|
|
21
|
+
error_messages:
|
|
22
|
+
- "Warning: The `save-state` command is deprecated and will be disabled soon. Please upgrade to using Environment Files."
|
|
23
|
+
- "Warning: The `get-state` command is deprecated and will be disabled soon. Please upgrade to using Environment Files."
|
|
24
|
+
root_cause: |
|
|
25
|
+
GitHub Actions originally provided workflow commands ::save-state name=FOO::value and
|
|
26
|
+
::get-state name=FOO:: as a way to persist data between the main body of a step and its
|
|
27
|
+
pre: or post: hooks in JavaScript actions.
|
|
28
|
+
|
|
29
|
+
GitHub deprecated these commands when it introduced environment files (GITHUB_STATE,
|
|
30
|
+
GITHUB_ENV, GITHUB_OUTPUT) as a more robust alternative. Workflows and actions that use
|
|
31
|
+
the old echo "::save-state name=...::value" or echo "::get-state name=...:" syntax now
|
|
32
|
+
produce deprecation warnings in workflow logs.
|
|
33
|
+
|
|
34
|
+
When these commands are eventually disabled, steps using them will silently fail to persist
|
|
35
|
+
state — the post: hook will read an empty string from the state variable, causing subtle
|
|
36
|
+
logic errors. Actions published to the Marketplace are particularly affected because older
|
|
37
|
+
versions used these commands before the deprecation was announced.
|
|
38
|
+
fix: |
|
|
39
|
+
Replace the deprecated workflow commands with the GITHUB_STATE environment file approach:
|
|
40
|
+
- To write state: append "KEY=value" to $GITHUB_STATE (shell) or write to
|
|
41
|
+
process.env['GITHUB_STATE'] (Node.js)
|
|
42
|
+
- To read state in pre:/post: hooks: read the environment variable STATE_<KEY>
|
|
43
|
+
|
|
44
|
+
In shell-based steps the pattern is identical to GITHUB_ENV and GITHUB_OUTPUT.
|
|
45
|
+
In JavaScript actions, upgrade to @actions/core v1.10.0+ and use the saveState() and
|
|
46
|
+
getState() helpers, which internally write to GITHUB_STATE.
|
|
47
|
+
fix_code:
|
|
48
|
+
- language: yaml
|
|
49
|
+
label: "Deprecated: ::save-state:: and ::get-state:: commands (produces warnings)"
|
|
50
|
+
code: |
|
|
51
|
+
# DEPRECATED — produces warning in logs, will eventually break
|
|
52
|
+
- name: Save state (deprecated)
|
|
53
|
+
run: echo "::save-state name=SERVER_PID::12345"
|
|
54
|
+
|
|
55
|
+
- name: Read state in post step (deprecated)
|
|
56
|
+
run: echo "Saved PID was $(echo "::get-state name=SERVER_PID::")"
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: "Correct: GITHUB_STATE environment file"
|
|
59
|
+
code: |
|
|
60
|
+
# Write state to GITHUB_STATE file
|
|
61
|
+
- name: Save state
|
|
62
|
+
run: echo "SERVER_PID=12345" >> $GITHUB_STATE
|
|
63
|
+
|
|
64
|
+
# State is automatically available as STATE_<KEY> in later phases
|
|
65
|
+
- name: Read state (available in main, pre, and post phases)
|
|
66
|
+
run: echo "Server PID was $STATE_SERVER_PID"
|
|
67
|
+
- language: yaml
|
|
68
|
+
label: "PowerShell equivalent"
|
|
69
|
+
code: |
|
|
70
|
+
- name: Save state (PowerShell)
|
|
71
|
+
shell: pwsh
|
|
72
|
+
run: |
|
|
73
|
+
"SERVER_PID=12345" | Out-File -FilePath $env:GITHUB_STATE -Append
|
|
74
|
+
|
|
75
|
+
- name: Read state (PowerShell)
|
|
76
|
+
shell: pwsh
|
|
77
|
+
run: |
|
|
78
|
+
Write-Host "Server PID was $env:STATE_SERVER_PID"
|
|
79
|
+
prevention:
|
|
80
|
+
- "Audit actions with pre: or post: hooks — these are the primary consumers of save-state/get-state"
|
|
81
|
+
- "Upgrade to @actions/core v1.10.0+ which uses GITHUB_STATE internally via saveState()/getState()"
|
|
82
|
+
- "Search existing workflows and composite actions for '::save-state' and '::get-state' patterns before they are disabled"
|
|
83
|
+
- "Pin to action versions that have migrated to GITHUB_STATE; check CHANGELOG for migration notes"
|
|
84
|
+
- "The same deprecation cycle affected ::set-output:: (now GITHUB_OUTPUT) and ::set-env:: (now GITHUB_ENV)"
|
|
85
|
+
docs:
|
|
86
|
+
- url: "https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files"
|
|
87
|
+
label: "GitHub Docs: Workflow commands — environment files"
|
|
88
|
+
- url: "https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/"
|
|
89
|
+
label: "GitHub Changelog: Deprecating save-state and set-output commands (Oct 2022)"
|
|
90
|
+
- url: "https://github.com/actions/toolkit/releases/tag/%40actions%2Fcore%401.10.0"
|
|
91
|
+
label: "actions/toolkit: @actions/core v1.10.0 release notes"
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
id: yaml-syntax-039
|
|
2
|
+
title: "'type: choice' not supported for workflow_call inputs — validation error when combining with workflow_dispatch"
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- workflow_call
|
|
7
|
+
- workflow_dispatch
|
|
8
|
+
- inputs
|
|
9
|
+
- type-choice
|
|
10
|
+
- validation
|
|
11
|
+
- combined-triggers
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Input type .choice. is not supported'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'Unexpected value .choice.'
|
|
16
|
+
flags: i
|
|
17
|
+
- regex: 'Invalid workflow file.*type.*choice'
|
|
18
|
+
flags: i
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Invalid workflow file: Input type 'choice' is not supported for workflow_call triggers. Allowed types: boolean, number, string"
|
|
21
|
+
- "Unexpected value 'choice', expected one of: boolean, number, string"
|
|
22
|
+
- "Invalid value for inputs.*.type: 'choice'"
|
|
23
|
+
root_cause: |
|
|
24
|
+
GitHub Actions supports different input types depending on the trigger:
|
|
25
|
+
- workflow_dispatch supports: boolean, choice, environment, number, string
|
|
26
|
+
- workflow_call supports only: boolean, number, string
|
|
27
|
+
|
|
28
|
+
When developers combine both triggers in the same workflow file (a common pattern for
|
|
29
|
+
workflows that can be called by other workflows AND dispatched manually), they define a
|
|
30
|
+
shared inputs: block. If those shared inputs use type: choice — valid for workflow_dispatch
|
|
31
|
+
but not workflow_call — the workflow fails schema validation with a cryptic error.
|
|
32
|
+
|
|
33
|
+
The error message does not always clearly indicate which trigger is rejecting the type,
|
|
34
|
+
causing developers to search for choice support across GitHub Actions documentation rather
|
|
35
|
+
than recognising it as a trigger-specific limitation. The same restriction applies to
|
|
36
|
+
type: environment.
|
|
37
|
+
fix: |
|
|
38
|
+
For inputs shared between workflow_dispatch and workflow_call, use type: string and
|
|
39
|
+
document the allowed values in the description field. The GitHub UI renders a free-text
|
|
40
|
+
field for manual dispatch instead of a dropdown, but callers still pass controlled values.
|
|
41
|
+
|
|
42
|
+
For strict input validation, add a shell step at the start of the job that fails if the
|
|
43
|
+
input value is not in the allowed set.
|
|
44
|
+
|
|
45
|
+
If the dropdown UI is essential for manual dispatch, create a thin dispatcher workflow
|
|
46
|
+
(workflow_dispatch only with type: choice) that calls the main reusable workflow passing
|
|
47
|
+
the validated value as a string.
|
|
48
|
+
fix_code:
|
|
49
|
+
- language: yaml
|
|
50
|
+
label: "Problem: type: choice breaks workflow_call validation"
|
|
51
|
+
code: |
|
|
52
|
+
on:
|
|
53
|
+
workflow_dispatch:
|
|
54
|
+
inputs:
|
|
55
|
+
environment:
|
|
56
|
+
type: choice # Valid for workflow_dispatch only
|
|
57
|
+
options: [dev, staging, prod]
|
|
58
|
+
workflow_call:
|
|
59
|
+
inputs:
|
|
60
|
+
environment:
|
|
61
|
+
type: choice # INVALID for workflow_call — causes validation error
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: "Fix: use type: string with documented allowed values"
|
|
64
|
+
code: |
|
|
65
|
+
on:
|
|
66
|
+
workflow_dispatch:
|
|
67
|
+
inputs:
|
|
68
|
+
environment:
|
|
69
|
+
type: string
|
|
70
|
+
description: "Target environment. Allowed values: dev, staging, prod"
|
|
71
|
+
default: dev
|
|
72
|
+
workflow_call:
|
|
73
|
+
inputs:
|
|
74
|
+
environment:
|
|
75
|
+
type: string
|
|
76
|
+
description: "Target environment. Allowed values: dev, staging, prod"
|
|
77
|
+
|
|
78
|
+
jobs:
|
|
79
|
+
deploy:
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
steps:
|
|
82
|
+
- name: Validate environment input
|
|
83
|
+
run: |
|
|
84
|
+
case "${{ inputs.environment }}" in
|
|
85
|
+
dev|staging|prod) echo "Environment: ${{ inputs.environment }}" ;;
|
|
86
|
+
*) echo "Invalid environment: ${{ inputs.environment }}"; exit 1 ;;
|
|
87
|
+
esac
|
|
88
|
+
- language: yaml
|
|
89
|
+
label: "Alternative: dispatcher wrapper preserves dropdown UI for manual runs"
|
|
90
|
+
code: |
|
|
91
|
+
# dispatcher.yml — manual dispatch wrapper with choice dropdown
|
|
92
|
+
on:
|
|
93
|
+
workflow_dispatch:
|
|
94
|
+
inputs:
|
|
95
|
+
environment:
|
|
96
|
+
type: choice
|
|
97
|
+
options: [dev, staging, prod]
|
|
98
|
+
|
|
99
|
+
jobs:
|
|
100
|
+
dispatch:
|
|
101
|
+
uses: ./.github/workflows/deploy.yml # calls the reusable workflow
|
|
102
|
+
with:
|
|
103
|
+
environment: ${{ inputs.environment }} # passes as string
|
|
104
|
+
prevention:
|
|
105
|
+
- "workflow_call inputs support only: boolean, number, string — not choice or environment"
|
|
106
|
+
- "Use type: string with a description listing allowed values when an input is shared across both triggers"
|
|
107
|
+
- "Add input validation as the first job step to fail fast when an unexpected value is passed via workflow_call"
|
|
108
|
+
- "Run actionlint locally before pushing — it catches unsupported input types at development time"
|
|
109
|
+
docs:
|
|
110
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onworkflow_callinputs"
|
|
111
|
+
label: "GitHub Docs: on.workflow_call.inputs syntax"
|
|
112
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onworkflow_dispatchinputs"
|
|
113
|
+
label: "GitHub Docs: on.workflow_dispatch.inputs syntax"
|
|
114
|
+
- url: "https://rhysd.github.io/actionlint/"
|
|
115
|
+
label: "actionlint: static checker for GitHub Actions workflow files"
|
package/package.json
CHANGED