@htekdev/actions-debugger 1.0.72 → 1.0.74
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/cache-post-step-skipped-on-job-failure.yml +127 -0
- package/errors/concurrency-timing/push-pr-ref-format-different-concurrency-no-sharing.yml +113 -0
- package/errors/known-unsolved/github-step-summary-not-accessible-cross-job.yml +132 -0
- package/errors/silent-failures/github-ref-name-pr-merge-ref-not-branch.yml +102 -0
- package/errors/silent-failures/reusable-workflow-caller-env-not-forwarded.yml +93 -0
- package/errors/triggers/issue-comment-pr-review-thread-not-triggered.yml +105 -0
- package/errors/yaml-syntax/branches-and-branches-ignore-combined-same-event.yml +99 -0
- package/errors/yaml-syntax/workflow-call-inputs-no-choice-type.yml +121 -0
- package/package.json +1 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
id: caching-artifacts-044
|
|
2
|
+
title: 'actions/cache post step skipped when job fails — cache not saved on failure (post-if: success() default)'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- actions-cache
|
|
7
|
+
- cache-save
|
|
8
|
+
- job-failure
|
|
9
|
+
- post-if
|
|
10
|
+
- save-always
|
|
11
|
+
- silent-skip
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Post\s+Run\s+actions/cache'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'save-always'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'Cache\s+not\s+found'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Cache was not saved. Exiting with failure."
|
|
21
|
+
- "Post Run actions/cache@v4 skipped"
|
|
22
|
+
- "Cache miss on subsequent run after failed job"
|
|
23
|
+
- "cache-hit: false even though previous run had cache action"
|
|
24
|
+
root_cause: |
|
|
25
|
+
actions/cache (v3 and v4) uses a `post-if: success()` condition on the cache save post step
|
|
26
|
+
by default. This means the cache save step only runs when ALL preceding steps in the job
|
|
27
|
+
succeeded. When any step fails:
|
|
28
|
+
- The job transitions to "failure" state
|
|
29
|
+
- The cache post step is SKIPPED silently — no warning, annotation, or error is logged
|
|
30
|
+
- The next run gets a cache miss and must re-download/rebuild from scratch
|
|
31
|
+
|
|
32
|
+
This is commonly discovered when:
|
|
33
|
+
- A test suite installs dependencies (cached) then runs tests (which fail)
|
|
34
|
+
- The cache works on the first run but never again after a flaky failure
|
|
35
|
+
- A build step fails, wasting all compilation work that could have been cached
|
|
36
|
+
- CI is slow because each failed run discards the cache and rebuilds from zero
|
|
37
|
+
|
|
38
|
+
Note: This is DIFFERENT from caching-artifacts-036 which covers the case where the job
|
|
39
|
+
is CANCELLED (via concurrency cancel-in-progress). Cancellation also skips the post step,
|
|
40
|
+
but for a different reason — cancellation interrupts all runners immediately.
|
|
41
|
+
|
|
42
|
+
The failure case is separate: the job runs to completion (with a failure), the post step
|
|
43
|
+
lifecycle still runs, but the post-if condition evaluates to false and skips it silently.
|
|
44
|
+
fix: |
|
|
45
|
+
Option 1 (recommended): Set `save-always: true` on the restore step (actions/cache v3.3.3+ and v4+):
|
|
46
|
+
- uses: actions/cache@v4
|
|
47
|
+
with:
|
|
48
|
+
path: ~/.npm
|
|
49
|
+
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
|
|
50
|
+
save-always: true # Save cache even if job fails
|
|
51
|
+
|
|
52
|
+
Option 2: Use the standalone cache/save action in an explicit cleanup step:
|
|
53
|
+
- uses: actions/cache/restore@v4
|
|
54
|
+
id: cache-restore
|
|
55
|
+
with:
|
|
56
|
+
path: ~/.npm
|
|
57
|
+
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
|
|
58
|
+
- run: npm ci
|
|
59
|
+
- uses: actions/cache/save@v4
|
|
60
|
+
if: always() # Explicit save even on failure
|
|
61
|
+
with:
|
|
62
|
+
path: ~/.npm
|
|
63
|
+
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
|
|
64
|
+
|
|
65
|
+
Option 3 (v3 only): Use `cache-v3-save-always-unexpected-input` — note that `save-always`
|
|
66
|
+
was not available before v3.3.3. On older versions, use the split restore/save approach.
|
|
67
|
+
fix_code:
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: 'Save cache on job failure using save-always: true (v4)'
|
|
70
|
+
code: |
|
|
71
|
+
jobs:
|
|
72
|
+
build:
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
steps:
|
|
75
|
+
- uses: actions/checkout@v4
|
|
76
|
+
|
|
77
|
+
- uses: actions/cache@v4
|
|
78
|
+
with:
|
|
79
|
+
path: ~/.npm
|
|
80
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
81
|
+
restore-keys: |
|
|
82
|
+
${{ runner.os }}-npm-
|
|
83
|
+
save-always: true # Cache saves even if subsequent steps fail
|
|
84
|
+
|
|
85
|
+
- run: npm ci
|
|
86
|
+
- run: npm test # If this fails, cache is still saved
|
|
87
|
+
- language: yaml
|
|
88
|
+
label: 'Explicit restore then save with if: always() (works on all versions)'
|
|
89
|
+
code: |
|
|
90
|
+
jobs:
|
|
91
|
+
build:
|
|
92
|
+
runs-on: ubuntu-latest
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@v4
|
|
95
|
+
|
|
96
|
+
- name: Restore cache
|
|
97
|
+
id: npm-cache
|
|
98
|
+
uses: actions/cache/restore@v4
|
|
99
|
+
with:
|
|
100
|
+
path: ~/.npm
|
|
101
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
102
|
+
restore-keys: |
|
|
103
|
+
${{ runner.os }}-npm-
|
|
104
|
+
|
|
105
|
+
- run: npm ci
|
|
106
|
+
- run: npm test
|
|
107
|
+
|
|
108
|
+
- name: Save cache (always, even on failure)
|
|
109
|
+
uses: actions/cache/save@v4
|
|
110
|
+
if: always()
|
|
111
|
+
with:
|
|
112
|
+
path: ~/.npm
|
|
113
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
114
|
+
prevention:
|
|
115
|
+
- 'Add save-always: true to actions/cache when partial installs (e.g., npm ci with an error) are still worth caching'
|
|
116
|
+
- 'Consider whether caching a partial/failed state is desirable — for compiled artifacts, only cache on success'
|
|
117
|
+
- 'Use the split restore/save action pattern for fine-grained control over when the cache is saved'
|
|
118
|
+
- 'Monitor cache hit rates in the Actions UI — consistently low rates after failures indicate post-if skips'
|
|
119
|
+
docs:
|
|
120
|
+
- url: 'https://github.com/actions/cache#save-cache-even-if-the-build-fails'
|
|
121
|
+
label: 'actions/cache — save-always: true option'
|
|
122
|
+
- url: 'https://github.com/actions/cache/blob/main/save/README.md'
|
|
123
|
+
label: 'actions/cache/save — standalone save action'
|
|
124
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows'
|
|
125
|
+
label: 'GitHub Docs — Caching dependencies to speed up workflows'
|
|
126
|
+
- url: 'https://github.com/actions/cache/issues/92'
|
|
127
|
+
label: 'actions/cache#92 — Feature request: save cache on failure (highly voted)'
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
id: concurrency-timing-038
|
|
2
|
+
title: 'push and pull_request events use different github.ref formats — concurrency slots never shared between event types'
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- concurrency
|
|
7
|
+
- github-ref
|
|
8
|
+
- pull-request
|
|
9
|
+
- push
|
|
10
|
+
- cancel-in-progress
|
|
11
|
+
- slot-sharing
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'group:\s+\$\{\{\s*github\.workflow\s*\}\}-\$\{\{\s*github\.ref\s*\}\}'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'refs/pull/\d+/merge'
|
|
16
|
+
flags: ''
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Push deploy and PR deploy run in parallel despite same concurrency group"
|
|
19
|
+
- "concurrency group does not prevent push and pull_request from running simultaneously"
|
|
20
|
+
- "Both workflows triggered at the same time even with concurrency group set"
|
|
21
|
+
root_cause: |
|
|
22
|
+
When concurrency group uses `${{ github.ref }}`, the ref value differs by event type:
|
|
23
|
+
- `on: push` to main → github.ref = 'refs/heads/main'
|
|
24
|
+
- `on: pull_request` targeting main → github.ref = 'refs/pull/123/merge' (synthetic merge ref)
|
|
25
|
+
|
|
26
|
+
These produce different concurrency group keys, so push and pull_request runs NEVER share
|
|
27
|
+
a concurrency slot. Developers who want "one deploy at a time" across push and PR events
|
|
28
|
+
are surprised when both run in parallel.
|
|
29
|
+
|
|
30
|
+
Common scenario:
|
|
31
|
+
- Deploy workflow triggers on both push (to main) and pull_request (for preview)
|
|
32
|
+
- A push to main is deploying
|
|
33
|
+
- A new PR triggers a deploy
|
|
34
|
+
- Both run simultaneously because their github.ref values are different strings
|
|
35
|
+
- Race condition or resource contention on the deployment target
|
|
36
|
+
|
|
37
|
+
This is distinct from the `github.head_ref` empty-on-push issue (which over-cancels).
|
|
38
|
+
Here, using `github.ref` under-cancels by never sharing slots between event types.
|
|
39
|
+
fix: |
|
|
40
|
+
To serialize push and pull_request deploys into one slot, use a ref-agnostic key
|
|
41
|
+
or normalize the ref to a shared value.
|
|
42
|
+
|
|
43
|
+
Option 1: Use a static concurrency key scoped to just the workflow name (one slot total):
|
|
44
|
+
group: ${{ github.workflow }}
|
|
45
|
+
cancel-in-progress: true
|
|
46
|
+
|
|
47
|
+
Option 2: Normalize to the target/base branch using head_ref for PRs and ref_name for push:
|
|
48
|
+
group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
|
|
49
|
+
cancel-in-progress: true
|
|
50
|
+
|
|
51
|
+
For github.head_ref || github.ref_name:
|
|
52
|
+
- pull_request: github.head_ref = 'feature/my-branch' (PR source branch)
|
|
53
|
+
- push: github.ref_name = 'main' (pushed branch name)
|
|
54
|
+
- Both give meaningful branch names, but they still differ — PRs use source branch, push uses target
|
|
55
|
+
- For "one deploy to main at a time" across both events, use github.base_ref || github.ref_name
|
|
56
|
+
|
|
57
|
+
Option 3: Use the target/base branch for both:
|
|
58
|
+
group: ${{ github.workflow }}-${{ github.base_ref || github.ref_name }}
|
|
59
|
+
cancel-in-progress: true
|
|
60
|
+
- pull_request: github.base_ref = 'main' (target branch)
|
|
61
|
+
- push: github.ref_name = 'main' (pushed branch)
|
|
62
|
+
- Both produce the same key for operations targeting main — serializes push and PR deploys
|
|
63
|
+
fix_code:
|
|
64
|
+
- language: yaml
|
|
65
|
+
label: 'Serialize push and pull_request deploys to the same target branch'
|
|
66
|
+
code: |
|
|
67
|
+
on:
|
|
68
|
+
push:
|
|
69
|
+
branches: [main]
|
|
70
|
+
pull_request:
|
|
71
|
+
branches: [main]
|
|
72
|
+
|
|
73
|
+
concurrency:
|
|
74
|
+
# Use base_ref (PR target) or ref_name (push branch) — both equal 'main'
|
|
75
|
+
# when targeting main, so push and PR deploys share one slot
|
|
76
|
+
group: ${{ github.workflow }}-${{ github.base_ref || github.ref_name }}
|
|
77
|
+
cancel-in-progress: true
|
|
78
|
+
|
|
79
|
+
jobs:
|
|
80
|
+
deploy:
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
steps:
|
|
83
|
+
- name: Deploy
|
|
84
|
+
run: echo "Deploying to ${{ github.base_ref || github.ref_name }}"
|
|
85
|
+
- language: yaml
|
|
86
|
+
label: 'One global slot for the entire workflow (simplest serialization)'
|
|
87
|
+
code: |
|
|
88
|
+
on:
|
|
89
|
+
push:
|
|
90
|
+
branches: [main]
|
|
91
|
+
pull_request:
|
|
92
|
+
|
|
93
|
+
concurrency:
|
|
94
|
+
group: ${{ github.workflow }}
|
|
95
|
+
cancel-in-progress: true
|
|
96
|
+
|
|
97
|
+
jobs:
|
|
98
|
+
build:
|
|
99
|
+
runs-on: ubuntu-latest
|
|
100
|
+
steps:
|
|
101
|
+
- run: echo "Only one run at a time, any trigger"
|
|
102
|
+
prevention:
|
|
103
|
+
- 'Understand that github.ref differs between push (refs/heads/BRANCH) and pull_request (refs/pull/N/merge) events'
|
|
104
|
+
- 'Use github.base_ref || github.ref_name to get the target/base branch name for both event types'
|
|
105
|
+
- 'Test concurrency behavior with simultaneous push and PR triggers before relying on it for deploy serialization'
|
|
106
|
+
- 'Use github.head_ref for the PR source branch or github.ref_name for push branch — never github.ref directly in concurrency keys unless same-event-type serialization is the goal'
|
|
107
|
+
docs:
|
|
108
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/controlling-concurrency'
|
|
109
|
+
label: 'GitHub Actions — Controlling concurrency'
|
|
110
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#github-context'
|
|
111
|
+
label: 'GitHub context — github.ref, github.head_ref, github.base_ref, github.ref_name'
|
|
112
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request'
|
|
113
|
+
label: 'pull_request event — github.ref is refs/pull/N/merge'
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
id: known-unsolved-046
|
|
2
|
+
title: 'GITHUB_STEP_SUMMARY content is not accessible by downstream jobs or reusable workflows'
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- github-step-summary
|
|
7
|
+
- step-summary
|
|
8
|
+
- cross-job
|
|
9
|
+
- job-outputs
|
|
10
|
+
- reusable-workflow
|
|
11
|
+
- limitation
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'GITHUB_STEP_SUMMARY'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'step.summary'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Cannot read GITHUB_STEP_SUMMARY from another job"
|
|
19
|
+
- "Step summary not available as job output"
|
|
20
|
+
- "How to share step summary content between jobs"
|
|
21
|
+
root_cause: |
|
|
22
|
+
`GITHUB_STEP_SUMMARY` is a write-only file path for adding Markdown content to the job
|
|
23
|
+
summary page in the GitHub Actions UI. The content written to it is visible in the UI
|
|
24
|
+
but is NOT accessible programmatically from:
|
|
25
|
+
- Other jobs in the same workflow (even with `needs:` dependency)
|
|
26
|
+
- Downstream caller workflows that invoke this as a reusable workflow
|
|
27
|
+
- Workflow run APIs — the summary content cannot be retrieved via the REST API
|
|
28
|
+
|
|
29
|
+
The summary file exists only on the runner's local filesystem during execution and is
|
|
30
|
+
uploaded to GitHub as display-only content. There is no `steps.<id>.summary` output
|
|
31
|
+
or `needs.<job>.summary` context available.
|
|
32
|
+
|
|
33
|
+
Common failure patterns:
|
|
34
|
+
- Build job writes a changelog summary; deploy job tries to use it in a notification
|
|
35
|
+
- Reusable workflow summarizes test results; caller tries to aggregate summaries
|
|
36
|
+
- Reporting job expects to read markdown generated by upstream jobs
|
|
37
|
+
|
|
38
|
+
GitHub has no native mechanism to read back summary content once written.
|
|
39
|
+
This is a long-standing requested feature with no announced roadmap.
|
|
40
|
+
fix: |
|
|
41
|
+
Use job outputs or artifacts to share data that needs to be readable by downstream jobs.
|
|
42
|
+
GITHUB_STEP_SUMMARY is display-only — treat it as a write-once UI annotation.
|
|
43
|
+
|
|
44
|
+
Workaround 1 — Dual-write (write to both summary AND output/artifact):
|
|
45
|
+
Write the same content to GITHUB_STEP_SUMMARY for display AND to $GITHUB_OUTPUT or
|
|
46
|
+
an artifact for programmatic consumption by downstream jobs.
|
|
47
|
+
|
|
48
|
+
Workaround 2 — Generate in a script and capture output:
|
|
49
|
+
Run the data-generation logic in a script, capture to a variable, write to both
|
|
50
|
+
$GITHUB_STEP_SUMMARY and $GITHUB_OUTPUT in the same step.
|
|
51
|
+
|
|
52
|
+
There is no workaround that reads GITHUB_STEP_SUMMARY after it has been written.
|
|
53
|
+
Data sharing between jobs MUST use job outputs or artifacts.
|
|
54
|
+
fix_code:
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: 'Dual-write: populate GITHUB_STEP_SUMMARY for UI AND job output for downstream use'
|
|
57
|
+
code: |
|
|
58
|
+
jobs:
|
|
59
|
+
build:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
outputs:
|
|
62
|
+
changelog: ${{ steps.gen.outputs.changelog }}
|
|
63
|
+
steps:
|
|
64
|
+
- name: Generate changelog
|
|
65
|
+
id: gen
|
|
66
|
+
run: |
|
|
67
|
+
CHANGES=$(git log --oneline HEAD~5..HEAD)
|
|
68
|
+
|
|
69
|
+
# Write to step summary for UI display
|
|
70
|
+
echo "## Changes" >> $GITHUB_STEP_SUMMARY
|
|
71
|
+
echo "$CHANGES" >> $GITHUB_STEP_SUMMARY
|
|
72
|
+
|
|
73
|
+
# Also write to output for downstream job consumption
|
|
74
|
+
# Use heredoc for multiline output
|
|
75
|
+
{
|
|
76
|
+
echo "changelog<<EOF"
|
|
77
|
+
echo "$CHANGES"
|
|
78
|
+
echo "EOF"
|
|
79
|
+
} >> $GITHUB_OUTPUT
|
|
80
|
+
|
|
81
|
+
notify:
|
|
82
|
+
needs: build
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
steps:
|
|
85
|
+
- name: Use changelog from build job
|
|
86
|
+
run: |
|
|
87
|
+
echo "Changes to notify: ${{ needs.build.outputs.changelog }}"
|
|
88
|
+
- language: yaml
|
|
89
|
+
label: 'Upload summary content as artifact for reusable workflow callers'
|
|
90
|
+
code: |
|
|
91
|
+
jobs:
|
|
92
|
+
test:
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
steps:
|
|
95
|
+
- name: Run tests and generate report
|
|
96
|
+
run: |
|
|
97
|
+
# Write display summary
|
|
98
|
+
echo "## Test Results" >> $GITHUB_STEP_SUMMARY
|
|
99
|
+
echo "All tests passed" >> $GITHUB_STEP_SUMMARY
|
|
100
|
+
|
|
101
|
+
# Write machine-readable copy to a file (upload as artifact)
|
|
102
|
+
echo "All tests passed" > test-summary.md
|
|
103
|
+
|
|
104
|
+
- name: Upload summary as artifact
|
|
105
|
+
uses: actions/upload-artifact@v4
|
|
106
|
+
with:
|
|
107
|
+
name: test-summary
|
|
108
|
+
path: test-summary.md
|
|
109
|
+
|
|
110
|
+
deploy:
|
|
111
|
+
needs: test
|
|
112
|
+
runs-on: ubuntu-latest
|
|
113
|
+
steps:
|
|
114
|
+
- name: Download test summary
|
|
115
|
+
uses: actions/download-artifact@v4
|
|
116
|
+
with:
|
|
117
|
+
name: test-summary
|
|
118
|
+
|
|
119
|
+
- name: Use test summary
|
|
120
|
+
run: cat test-summary.md
|
|
121
|
+
prevention:
|
|
122
|
+
- 'Never assume GITHUB_STEP_SUMMARY content can be read back — it is write-only display output'
|
|
123
|
+
- 'Design workflows to use job outputs ($GITHUB_OUTPUT) or artifacts for any data that must flow between jobs'
|
|
124
|
+
- 'Write data to both GITHUB_STEP_SUMMARY (for UI) and $GITHUB_OUTPUT (for downstream) in the same step when both are needed'
|
|
125
|
+
- 'Document this limitation for team members who discover it while trying to aggregate summaries across jobs'
|
|
126
|
+
docs:
|
|
127
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#adding-a-job-summary'
|
|
128
|
+
label: 'GitHub Docs — Adding a job summary (GITHUB_STEP_SUMMARY)'
|
|
129
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs'
|
|
130
|
+
label: 'GitHub Docs — Passing information between jobs (outputs and artifacts)'
|
|
131
|
+
- url: 'https://github.com/orgs/community/discussions/12836'
|
|
132
|
+
label: 'GitHub Community — Read GITHUB_STEP_SUMMARY from downstream jobs (no native support)'
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
id: silent-failures-070
|
|
2
|
+
title: 'github.ref_name returns N/merge on pull_request events, not the head branch name'
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- github-ref-name
|
|
7
|
+
- github-head-ref
|
|
8
|
+
- pull-request
|
|
9
|
+
- merge-ref
|
|
10
|
+
- branch-name
|
|
11
|
+
- context
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'github\.ref_name'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: '\d+/merge'
|
|
16
|
+
flags: ''
|
|
17
|
+
error_messages:
|
|
18
|
+
- "branch name is '15/merge' instead of 'feature/my-branch'"
|
|
19
|
+
- "invalid branch: 45/merge"
|
|
20
|
+
- "ref_name evaluates to 23/merge on pull_request trigger"
|
|
21
|
+
root_cause: |
|
|
22
|
+
On pull_request events, GitHub creates a synthetic merge commit ref at refs/pull/N/merge
|
|
23
|
+
representing the result of merging the head branch into the base branch. This is the ref
|
|
24
|
+
that the runner checks out by default.
|
|
25
|
+
|
|
26
|
+
github.ref resolves to 'refs/pull/N/merge' and github.ref_name resolves to 'N/merge'
|
|
27
|
+
(e.g., '15/merge') — not the human-readable head branch name.
|
|
28
|
+
|
|
29
|
+
Developers who use github.ref_name expecting the PR head branch name receive the synthetic
|
|
30
|
+
merge ref slug instead, which breaks:
|
|
31
|
+
- Branch-based deployment logic
|
|
32
|
+
- Conditional workflow steps that check the branch name
|
|
33
|
+
- Tag/release workflows incorrectly applied to PR builds
|
|
34
|
+
- Docker image tags derived from the branch name
|
|
35
|
+
|
|
36
|
+
To get the actual PR head branch name on pull_request events, use github.head_ref.
|
|
37
|
+
To get the base branch name, use github.base_ref.
|
|
38
|
+
|
|
39
|
+
Note: On push events, github.ref_name correctly returns the branch name (e.g., 'main').
|
|
40
|
+
The discrepancy only affects pull_request (and pull_request_target) events.
|
|
41
|
+
fix: |
|
|
42
|
+
Use github.head_ref instead of github.ref_name when you need the PR head branch name.
|
|
43
|
+
|
|
44
|
+
For robust multi-event workflows, use a conditional expression:
|
|
45
|
+
- On pull_request: github.head_ref contains the branch name
|
|
46
|
+
- On push: github.ref_name contains the branch name
|
|
47
|
+
|
|
48
|
+
Or use a ternary-style expression to normalize:
|
|
49
|
+
${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}
|
|
50
|
+
fix_code:
|
|
51
|
+
- language: yaml
|
|
52
|
+
label: 'Use github.head_ref for PR branch name, github.ref_name for push'
|
|
53
|
+
code: |
|
|
54
|
+
jobs:
|
|
55
|
+
build:
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
steps:
|
|
58
|
+
- name: Get branch name
|
|
59
|
+
run: |
|
|
60
|
+
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
61
|
+
BRANCH="${{ github.head_ref }}"
|
|
62
|
+
else
|
|
63
|
+
BRANCH="${{ github.ref_name }}"
|
|
64
|
+
fi
|
|
65
|
+
echo "Branch: $BRANCH"
|
|
66
|
+
echo "BRANCH=$BRANCH" >> $GITHUB_ENV
|
|
67
|
+
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: 'Normalize branch name in Docker image tag (works for push and PR)'
|
|
70
|
+
code: |
|
|
71
|
+
on:
|
|
72
|
+
push:
|
|
73
|
+
branches: [main, develop]
|
|
74
|
+
pull_request:
|
|
75
|
+
|
|
76
|
+
jobs:
|
|
77
|
+
docker:
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
steps:
|
|
80
|
+
- name: Set branch slug
|
|
81
|
+
id: branch
|
|
82
|
+
run: |
|
|
83
|
+
# Use head_ref for PRs, ref_name for push events
|
|
84
|
+
BRANCH="${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}"
|
|
85
|
+
# Slugify: replace / with - for Docker tag compatibility
|
|
86
|
+
SLUG=$(echo "$BRANCH" | tr '/' '-' | tr '[:upper:]' '[:lower:]')
|
|
87
|
+
echo "slug=$SLUG" >> $GITHUB_OUTPUT
|
|
88
|
+
|
|
89
|
+
- name: Build Docker image
|
|
90
|
+
run: docker build -t myapp:${{ steps.branch.outputs.slug }} .
|
|
91
|
+
prevention:
|
|
92
|
+
- 'On pull_request events, always use github.head_ref for the PR head branch and github.base_ref for the target base branch'
|
|
93
|
+
- 'github.ref_name is only reliable as a branch name on push events — never use it unconditionally'
|
|
94
|
+
- 'When writing multi-event workflows (push + pull_request), add an event-conditional expression to normalize the branch name'
|
|
95
|
+
- 'Test workflows triggered by both push and pull_request events to catch context differences early'
|
|
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 context — github.ref_name, github.head_ref, github.base_ref'
|
|
99
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request'
|
|
100
|
+
label: 'pull_request event context and merge ref behavior'
|
|
101
|
+
- url: 'https://github.com/orgs/community/discussions/25722'
|
|
102
|
+
label: 'GitHub Community: github.ref_name returns N/merge on pull_request'
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
id: silent-failures-069
|
|
2
|
+
title: 'workflow_call: caller env vars not forwarded to reusable workflow — silently unavailable'
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- reusable-workflow
|
|
7
|
+
- workflow_call
|
|
8
|
+
- env-context
|
|
9
|
+
- caller-env
|
|
10
|
+
- inputs
|
|
11
|
+
- environment-variables
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'env\.\w+ is not defined'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: '\$\{\{\s*env\.\w+\s*\}\}\s*$'
|
|
16
|
+
flags: 'im'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "env.API_URL is not defined"
|
|
19
|
+
- "The template is not valid. .github/workflows/deploy.yml: env.BASE_URL is not defined"
|
|
20
|
+
root_cause: |
|
|
21
|
+
GitHub Actions reusable workflows (on.workflow_call) run in a completely isolated environment.
|
|
22
|
+
The caller workflow's env: context — whether defined at the top-level workflow scope or at the
|
|
23
|
+
calling job scope — is NOT automatically forwarded to the called reusable workflow.
|
|
24
|
+
|
|
25
|
+
Only two mechanisms exist for passing data into a reusable workflow:
|
|
26
|
+
- inputs: (on.workflow_call.inputs) for plain values
|
|
27
|
+
- secrets: (on.workflow_call.secrets) for sensitive values
|
|
28
|
+
|
|
29
|
+
A common mistake is defining env: vars at the caller workflow level and assuming they will be
|
|
30
|
+
visible inside the reusable workflow. They are not. The called workflow has its own empty env:
|
|
31
|
+
context (unless vars.* or env: is set within the reusable workflow itself).
|
|
32
|
+
|
|
33
|
+
Example that breaks silently:
|
|
34
|
+
- Caller sets: env: { API_URL: 'https://api.example.com' }
|
|
35
|
+
- Caller calls: uses: ./.github/workflows/deploy.yml
|
|
36
|
+
- Inside deploy.yml: run: curl ${{ env.API_URL }} # evaluates to empty string, no error
|
|
37
|
+
fix: |
|
|
38
|
+
Pass values explicitly via the inputs: mechanism on the reusable workflow call.
|
|
39
|
+
Define the input on on.workflow_call.inputs and reference it as inputs.<name> inside the
|
|
40
|
+
called workflow.
|
|
41
|
+
|
|
42
|
+
If many env vars need forwarding, consider using a JSON-encoded input and fromJSON() inside
|
|
43
|
+
the called workflow, or use repository variables (vars.*) which are available in all workflows.
|
|
44
|
+
fix_code:
|
|
45
|
+
- language: yaml
|
|
46
|
+
label: 'Pass caller env var as explicit input to reusable workflow'
|
|
47
|
+
code: |
|
|
48
|
+
# Caller workflow
|
|
49
|
+
jobs:
|
|
50
|
+
deploy:
|
|
51
|
+
uses: ./.github/workflows/deploy.yml
|
|
52
|
+
with:
|
|
53
|
+
api_url: ${{ vars.API_URL }} # or hardcoded value
|
|
54
|
+
secrets: inherit
|
|
55
|
+
|
|
56
|
+
# Reusable workflow: .github/workflows/deploy.yml
|
|
57
|
+
on:
|
|
58
|
+
workflow_call:
|
|
59
|
+
inputs:
|
|
60
|
+
api_url:
|
|
61
|
+
required: true
|
|
62
|
+
type: string
|
|
63
|
+
|
|
64
|
+
jobs:
|
|
65
|
+
run:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
steps:
|
|
68
|
+
- name: Deploy
|
|
69
|
+
run: curl ${{ inputs.api_url }}
|
|
70
|
+
|
|
71
|
+
- language: yaml
|
|
72
|
+
label: 'Use repository variables (vars.*) accessible in all workflows'
|
|
73
|
+
code: |
|
|
74
|
+
# Set API_URL as a repository variable in Settings → Secrets and variables → Actions → Variables
|
|
75
|
+
# Then reference it in the reusable workflow directly:
|
|
76
|
+
jobs:
|
|
77
|
+
run:
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
steps:
|
|
80
|
+
- name: Deploy
|
|
81
|
+
run: curl ${{ vars.API_URL }}
|
|
82
|
+
prevention:
|
|
83
|
+
- 'Never rely on env: vars defined in a caller workflow being available inside a reusable workflow — they are not forwarded'
|
|
84
|
+
- 'Declare all cross-workflow values as on.workflow_call.inputs or on.workflow_call.secrets'
|
|
85
|
+
- 'Use repository/org-level variables (vars.*) for configuration that many workflows need without explicit passing'
|
|
86
|
+
- 'Run actionlint on caller and callee separately — it will flag undefined env references in reusable workflows'
|
|
87
|
+
docs:
|
|
88
|
+
- url: 'https://docs.github.com/en/actions/sharing-automations/reusing-workflows#passing-inputs-and-secrets-to-a-reusable-workflow'
|
|
89
|
+
label: 'Passing inputs and secrets to a reusable workflow'
|
|
90
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#using-the-vars-context-to-access-configuration-variable-values'
|
|
91
|
+
label: 'Using repository variables with the vars context'
|
|
92
|
+
- url: 'https://github.com/orgs/community/discussions/26671'
|
|
93
|
+
label: 'GitHub Community: env vars not accessible in reusable workflows'
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
id: triggers-050
|
|
2
|
+
title: 'on.issue_comment does not fire for PR review thread comments — requires pull_request_review_comment'
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- issue_comment
|
|
7
|
+
- pull_request_review_comment
|
|
8
|
+
- pr-review
|
|
9
|
+
- comment-event
|
|
10
|
+
- trigger
|
|
11
|
+
- chatops
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'on:\s*\n\s+issue_comment'
|
|
14
|
+
flags: 'im'
|
|
15
|
+
- regex: 'github\.event_name\s*==\s*[''"]issue_comment[''"]'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "workflow never triggered when commenting on a PR review thread"
|
|
19
|
+
- "issue_comment workflow fires on issue comments but not PR inline review comments"
|
|
20
|
+
root_cause: |
|
|
21
|
+
GitHub Actions has two distinct comment event types that developers frequently confuse:
|
|
22
|
+
|
|
23
|
+
1. issue_comment — fires when a comment is posted on:
|
|
24
|
+
- The main conversation thread of an issue
|
|
25
|
+
- The main conversation thread of a pull request (PR-as-issue comments)
|
|
26
|
+
It does NOT fire for inline PR code review comments or review summary comments.
|
|
27
|
+
|
|
28
|
+
2. pull_request_review_comment — fires when a comment is posted on:
|
|
29
|
+
- A specific line of code in a PR diff (inline review comment)
|
|
30
|
+
It does NOT fire for comments on the main PR conversation thread.
|
|
31
|
+
|
|
32
|
+
3. pull_request_review — fires when:
|
|
33
|
+
- A complete PR review is submitted (including review body, approval, or request-changes)
|
|
34
|
+
|
|
35
|
+
Chatops bots and automation that listen to issue_comment expecting to catch all PR comment
|
|
36
|
+
activity will silently miss all PR code review thread comments. There is no error — the
|
|
37
|
+
workflow simply never fires for that comment type.
|
|
38
|
+
|
|
39
|
+
Similarly, on.pull_request_review_comment will miss all main thread comments on the same PR.
|
|
40
|
+
fix: |
|
|
41
|
+
Choose the correct trigger based on what type of comment you want to react to:
|
|
42
|
+
|
|
43
|
+
- For commands in PR main conversation (/deploy, /approve, etc.): use issue_comment
|
|
44
|
+
- For reactions to inline code review comments: use pull_request_review_comment
|
|
45
|
+
- For reactions to full review submissions (approve, request changes): use pull_request_review
|
|
46
|
+
- For all PR comment activity: use both issue_comment AND pull_request_review_comment
|
|
47
|
+
|
|
48
|
+
Note: on.issue_comment has access to github.event.issue.number for the PR number,
|
|
49
|
+
while on.pull_request_review_comment has access to github.event.pull_request.number.
|
|
50
|
+
fix_code:
|
|
51
|
+
- language: yaml
|
|
52
|
+
label: 'Listen to all PR comment types (main thread + review thread)'
|
|
53
|
+
code: |
|
|
54
|
+
on:
|
|
55
|
+
issue_comment:
|
|
56
|
+
types: [created]
|
|
57
|
+
pull_request_review_comment:
|
|
58
|
+
types: [created]
|
|
59
|
+
pull_request_review:
|
|
60
|
+
types: [submitted]
|
|
61
|
+
|
|
62
|
+
jobs:
|
|
63
|
+
handle-comment:
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
steps:
|
|
66
|
+
- name: Handle comment
|
|
67
|
+
run: |
|
|
68
|
+
if [ "${{ github.event_name }}" = "issue_comment" ]; then
|
|
69
|
+
echo "Main thread comment: ${{ github.event.comment.body }}"
|
|
70
|
+
echo "PR number: ${{ github.event.issue.number }}"
|
|
71
|
+
elif [ "${{ github.event_name }}" = "pull_request_review_comment" ]; then
|
|
72
|
+
echo "Review thread comment: ${{ github.event.comment.body }}"
|
|
73
|
+
echo "PR number: ${{ github.event.pull_request.number }}"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
- language: yaml
|
|
77
|
+
label: 'Chatops bot — listen for slash commands on main PR thread only'
|
|
78
|
+
code: |
|
|
79
|
+
on:
|
|
80
|
+
issue_comment:
|
|
81
|
+
types: [created]
|
|
82
|
+
|
|
83
|
+
jobs:
|
|
84
|
+
chatops:
|
|
85
|
+
# Only run for PR comments (issue_comment also fires on plain issues)
|
|
86
|
+
if: github.event.issue.pull_request != null
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
steps:
|
|
89
|
+
- name: Check for slash command
|
|
90
|
+
run: |
|
|
91
|
+
echo "Comment body: ${{ github.event.comment.body }}"
|
|
92
|
+
prevention:
|
|
93
|
+
- 'Distinguish issue_comment (main PR thread) from pull_request_review_comment (inline code review thread) — they never overlap'
|
|
94
|
+
- 'If building a PR chatops bot, filter to PR-only comments by checking github.event.issue.pull_request != null'
|
|
95
|
+
- 'Use pull_request_review for reactions to full review submissions (approve/reject), not pull_request_review_comment'
|
|
96
|
+
- 'Test your trigger by posting comments in BOTH places (main conversation and code review thread) to verify expected behavior'
|
|
97
|
+
docs:
|
|
98
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#issue_comment'
|
|
99
|
+
label: 'GitHub Docs — issue_comment event'
|
|
100
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_review_comment'
|
|
101
|
+
label: 'GitHub Docs — pull_request_review_comment event'
|
|
102
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_review'
|
|
103
|
+
label: 'GitHub Docs — pull_request_review event'
|
|
104
|
+
- url: 'https://github.com/orgs/community/discussions/21929'
|
|
105
|
+
label: 'GitHub Community: issue_comment vs pull_request_review_comment confusion'
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
id: yaml-syntax-047
|
|
2
|
+
title: '"You can''t use both ''branches'' and ''branches-ignore''" — combined on same event trigger'
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- branches
|
|
7
|
+
- branches-ignore
|
|
8
|
+
- trigger-filter
|
|
9
|
+
- validation-error
|
|
10
|
+
- actionlint
|
|
11
|
+
- on-push
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'You can''t use both ''branches'' and ''branches-ignore'''
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: "You can't use both 'branches' and 'branches-ignore'"
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'branches.*branches-ignore|branches-ignore.*branches'
|
|
18
|
+
flags: 'is'
|
|
19
|
+
error_messages:
|
|
20
|
+
- "You can't use both 'branches' and 'branches-ignore' for the same event."
|
|
21
|
+
- "Invalid workflow file: .github/workflows/ci.yml: You can't use both 'branches' and 'branches-ignore' for the same event."
|
|
22
|
+
root_cause: |
|
|
23
|
+
GitHub Actions validates that branches and branches-ignore are mutually exclusive filters
|
|
24
|
+
on the same trigger event. Using both simultaneously causes a workflow parse error because
|
|
25
|
+
the semantics are contradictory — branches is an allowlist while branches-ignore is a
|
|
26
|
+
denylist, and combining them on the same trigger is undefined behavior.
|
|
27
|
+
|
|
28
|
+
This restriction applies to all events that support branch filtering, including:
|
|
29
|
+
- on.push
|
|
30
|
+
- on.pull_request
|
|
31
|
+
- on.pull_request_target
|
|
32
|
+
|
|
33
|
+
The same restriction applies to paths vs paths-ignore and tags vs tags-ignore.
|
|
34
|
+
|
|
35
|
+
Common mistake patterns:
|
|
36
|
+
- Developer adds branches-ignore: [main] to prevent duplicate runs, forgetting branches is set
|
|
37
|
+
- Developer copies a snippet with branches and adds branches-ignore without removing the original
|
|
38
|
+
- Workflow generated from a template that includes both filters from different sections
|
|
39
|
+
|
|
40
|
+
Note: branches and paths can coexist on the same trigger (both act as allowlists that AND together).
|
|
41
|
+
It is only branches + branches-ignore (or paths + paths-ignore) that conflict.
|
|
42
|
+
fix: |
|
|
43
|
+
Choose one approach and remove the other:
|
|
44
|
+
|
|
45
|
+
Option A — Use branches (allowlist): explicitly list which branches should trigger the workflow.
|
|
46
|
+
Option B — Use branches-ignore (denylist): list which branches should NOT trigger the workflow.
|
|
47
|
+
|
|
48
|
+
To achieve "run on all branches except main and release/*", use branches-ignore.
|
|
49
|
+
To achieve "only run on main and feature/*", use branches.
|
|
50
|
+
|
|
51
|
+
For complex patterns (e.g., "feature/* except feature/skip"), use branches with negation
|
|
52
|
+
via the ! prefix:
|
|
53
|
+
branches:
|
|
54
|
+
- 'feature/**'
|
|
55
|
+
- '!feature/skip-*'
|
|
56
|
+
fix_code:
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: 'Broken — branches and branches-ignore on same event (causes validation error)'
|
|
59
|
+
code: |
|
|
60
|
+
# BROKEN — do not use both on the same event
|
|
61
|
+
on:
|
|
62
|
+
push:
|
|
63
|
+
branches:
|
|
64
|
+
- main
|
|
65
|
+
- 'feature/**'
|
|
66
|
+
branches-ignore: # ERROR: cannot combine with branches
|
|
67
|
+
- 'feature/wip-*'
|
|
68
|
+
|
|
69
|
+
- language: yaml
|
|
70
|
+
label: 'Fixed — use branches with ! negation patterns'
|
|
71
|
+
code: |
|
|
72
|
+
on:
|
|
73
|
+
push:
|
|
74
|
+
branches:
|
|
75
|
+
- main
|
|
76
|
+
- 'feature/**'
|
|
77
|
+
- '!feature/wip-*' # negate specific sub-pattern
|
|
78
|
+
|
|
79
|
+
- language: yaml
|
|
80
|
+
label: 'Fixed — use branches-ignore (denylist) alone'
|
|
81
|
+
code: |
|
|
82
|
+
on:
|
|
83
|
+
push:
|
|
84
|
+
branches-ignore:
|
|
85
|
+
- 'feature/wip-*'
|
|
86
|
+
- 'dependabot/**'
|
|
87
|
+
prevention:
|
|
88
|
+
- 'Never combine branches and branches-ignore on the same trigger event — pick one approach'
|
|
89
|
+
- 'Use branches (allowlist) when only a few branches should trigger; use branches-ignore (denylist) when most branches should trigger'
|
|
90
|
+
- 'To exclude specific patterns from an allowlist, use ! negation prefix inside the branches list'
|
|
91
|
+
- 'Run actionlint in CI to catch this validation error before pushing (actionlint reports it immediately)'
|
|
92
|
+
- 'The same mutual-exclusion rule applies to paths/paths-ignore and tags/tags-ignore'
|
|
93
|
+
docs:
|
|
94
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/triggering-a-workflow#using-filters'
|
|
95
|
+
label: 'Using filters — branches, branches-ignore, and negation patterns'
|
|
96
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onpushbranchestagsbranches-ignoretags-ignore'
|
|
97
|
+
label: 'Workflow syntax — on.push branches and branches-ignore'
|
|
98
|
+
- url: 'https://rhysd.github.io/actionlint/'
|
|
99
|
+
label: 'actionlint — static checker that catches this error instantly'
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
id: yaml-syntax-048
|
|
2
|
+
title: 'workflow_call inputs do not support type: choice — validation error when copying from workflow_dispatch'
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- workflow-call
|
|
7
|
+
- reusable-workflow
|
|
8
|
+
- inputs
|
|
9
|
+
- type-choice
|
|
10
|
+
- workflow-dispatch
|
|
11
|
+
- validation-error
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Unexpected\s+value\s+[''"]choice[''"]'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'type:\s+choice'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'on\.workflow_call\.inputs\.[^.]+\.type.*choice'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Unexpected value 'choice'"
|
|
21
|
+
- "Invalid workflow file: .github/workflows/deploy.yml#L12: Unexpected value 'choice'"
|
|
22
|
+
- "on.workflow_call.inputs.environment.type: Unexpected value 'choice'"
|
|
23
|
+
- "inputs.environment.options: Unexpected value"
|
|
24
|
+
root_cause: |
|
|
25
|
+
GitHub Actions supports two input type systems that are NOT identical:
|
|
26
|
+
- workflow_dispatch inputs support: string, choice, boolean, environment, number
|
|
27
|
+
- workflow_call inputs support: string, boolean, number ONLY
|
|
28
|
+
|
|
29
|
+
The `choice` type (with its `options:` list) is exclusive to workflow_dispatch manual triggers.
|
|
30
|
+
Reusable workflows (workflow_call) do not support `type: choice` or the `options:` field.
|
|
31
|
+
|
|
32
|
+
This error is most commonly hit when:
|
|
33
|
+
1. A developer builds a manual workflow_dispatch workflow with choice inputs
|
|
34
|
+
2. They convert it to a reusable workflow by changing `on: workflow_dispatch` to `on: workflow_call`
|
|
35
|
+
3. They leave the `type: choice` and `options:` keys intact
|
|
36
|
+
4. The workflow fails validation with "Unexpected value 'choice'"
|
|
37
|
+
|
|
38
|
+
Example invalid workflow_call input:
|
|
39
|
+
on:
|
|
40
|
+
workflow_call:
|
|
41
|
+
inputs:
|
|
42
|
+
environment:
|
|
43
|
+
type: choice # ERROR: not supported in workflow_call
|
|
44
|
+
options: # ERROR: not supported in workflow_call
|
|
45
|
+
- staging
|
|
46
|
+
- production
|
|
47
|
+
required: true
|
|
48
|
+
fix: |
|
|
49
|
+
Change `type: choice` to `type: string` in workflow_call inputs and remove the `options:` key.
|
|
50
|
+
To enforce valid values, validate the input inside the workflow body using an `if:` condition
|
|
51
|
+
or a run step that exits on invalid input.
|
|
52
|
+
|
|
53
|
+
The calling workflow or documentation should enumerate the valid values since workflow_call
|
|
54
|
+
cannot enforce them at the input schema level.
|
|
55
|
+
fix_code:
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: 'workflow_call input with string type and manual validation (called workflow)'
|
|
58
|
+
code: |
|
|
59
|
+
# .github/workflows/deploy-reusable.yml
|
|
60
|
+
on:
|
|
61
|
+
workflow_call:
|
|
62
|
+
inputs:
|
|
63
|
+
environment:
|
|
64
|
+
type: string # Change 'choice' to 'string'
|
|
65
|
+
required: true
|
|
66
|
+
description: 'Target environment: staging or production'
|
|
67
|
+
# No 'options:' key — not supported in workflow_call
|
|
68
|
+
|
|
69
|
+
jobs:
|
|
70
|
+
validate-input:
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
steps:
|
|
73
|
+
- name: Validate environment input
|
|
74
|
+
run: |
|
|
75
|
+
if [[ "${{ inputs.environment }}" != "staging" && "${{ inputs.environment }}" != "production" ]]; then
|
|
76
|
+
echo "Invalid environment: ${{ inputs.environment }}"
|
|
77
|
+
echo "Valid values: staging, production"
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
deploy:
|
|
82
|
+
needs: validate-input
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
environment: ${{ inputs.environment }}
|
|
85
|
+
steps:
|
|
86
|
+
- run: echo "Deploying to ${{ inputs.environment }}"
|
|
87
|
+
- language: yaml
|
|
88
|
+
label: 'Calling workflow passing the environment string input'
|
|
89
|
+
code: |
|
|
90
|
+
# .github/workflows/deploy-caller.yml
|
|
91
|
+
on:
|
|
92
|
+
workflow_dispatch:
|
|
93
|
+
inputs:
|
|
94
|
+
environment:
|
|
95
|
+
type: choice # choice IS supported here in workflow_dispatch
|
|
96
|
+
options:
|
|
97
|
+
- staging
|
|
98
|
+
- production
|
|
99
|
+
required: true
|
|
100
|
+
|
|
101
|
+
jobs:
|
|
102
|
+
call-deploy:
|
|
103
|
+
uses: ./.github/workflows/deploy-reusable.yml
|
|
104
|
+
with:
|
|
105
|
+
environment: ${{ inputs.environment }} # Pass validated string to reusable
|
|
106
|
+
secrets: inherit
|
|
107
|
+
prevention:
|
|
108
|
+
- 'workflow_call inputs only support type: string, boolean, and number — never type: choice or type: environment'
|
|
109
|
+
- 'Keep workflow_dispatch wrappers that expose choice inputs and call the reusable workflow internally'
|
|
110
|
+
- 'Document the valid values for string inputs in the description field of workflow_call inputs'
|
|
111
|
+
- 'Add an explicit validation step at the start of the reusable workflow to enforce allowed values'
|
|
112
|
+
- 'Use actionlint to catch unsupported input types before pushing the workflow file'
|
|
113
|
+
docs:
|
|
114
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_call'
|
|
115
|
+
label: 'GitHub Docs — workflow_call inputs (string, boolean, number only)'
|
|
116
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_dispatch'
|
|
117
|
+
label: 'GitHub Docs — workflow_dispatch inputs (includes choice, environment types)'
|
|
118
|
+
- url: 'https://docs.github.com/en/actions/sharing-automations/reusing-workflows#using-inputs-and-secrets-in-a-reusable-workflow'
|
|
119
|
+
label: 'GitHub Docs — Reusable workflow inputs and secrets'
|
|
120
|
+
- url: 'https://github.com/orgs/community/discussions/39357'
|
|
121
|
+
label: 'GitHub Community — workflow_call does not support type: choice inputs'
|
package/package.json
CHANGED