@htekdev/actions-debugger 1.0.73 → 1.0.75
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/known-unsolved/reusable-workflow-local-action-path-resolves-to-called-repo.yml +114 -0
- package/errors/runner-environment/github-script-require-module-not-found.yml +98 -0
- package/errors/silent-failures/env-block-sibling-key-reference-empty-string.yml +92 -0
- package/errors/triggers/create-event-fires-for-branch-and-tag.yml +108 -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,114 @@
|
|
|
1
|
+
id: known-unsolved-047
|
|
2
|
+
title: "Reusable workflow uses: ./local-action resolves relative to the called repo, not the calling repo"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- reusable-workflow
|
|
7
|
+
- composite-action
|
|
8
|
+
- path-resolution
|
|
9
|
+
- local-action
|
|
10
|
+
- cross-repo
|
|
11
|
+
- limitation
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Can''t find ''action\.ya?ml'''
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'Unable to resolve action.*\.\/'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'action\.ya?ml.*does not exist'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under '/home/runner/work/repo/repo/local-action'"
|
|
21
|
+
- "Unable to resolve action `./local-action`, unable to find 'action.yaml' or 'action.yml'"
|
|
22
|
+
- "Error: Can't find 'action.yml' for step uses: ./shared-actions/deploy"
|
|
23
|
+
root_cause: |
|
|
24
|
+
When a reusable workflow file (stored in org/shared-workflows) contains steps that
|
|
25
|
+
reference local composite actions using relative paths (uses: ./path/to/action), the
|
|
26
|
+
path resolves relative to the CALLED (reusable) repository — not the CALLING
|
|
27
|
+
repository.
|
|
28
|
+
|
|
29
|
+
Resolution rule: In any workflow file, uses: ./path always resolves to the root of
|
|
30
|
+
the repository that CONTAINS the workflow YAML file.
|
|
31
|
+
|
|
32
|
+
So in org/shared-workflows/.github/workflows/ci.yml:
|
|
33
|
+
- uses: ./actions/lint → looks in org/shared-workflows/actions/lint/action.yml
|
|
34
|
+
- Does NOT look in org/app-repo/actions/lint/ (the calling repo)
|
|
35
|
+
|
|
36
|
+
This means teams cannot:
|
|
37
|
+
- Share a reusable workflow that calls composite actions defined in the CALLING repo
|
|
38
|
+
- Have callers "inject" local actions into a shared reusable workflow via relative paths
|
|
39
|
+
- Centralize reusable workflows in one repo while composite actions stay in team repos
|
|
40
|
+
|
|
41
|
+
This limitation is by design and has been acknowledged by the GitHub Actions team as
|
|
42
|
+
a fundamental architectural constraint. The calling repository's workspace may be
|
|
43
|
+
checked out during the job, but action path resolution happens at workflow evaluation
|
|
44
|
+
time using the workflow file's own repository root, not the runtime workspace.
|
|
45
|
+
fix: |
|
|
46
|
+
There is no direct fix — this is a platform-level path resolution limitation.
|
|
47
|
+
|
|
48
|
+
Option 1 (recommended) — Co-locate composite actions in the same repository as the
|
|
49
|
+
reusable workflow and reference them with uses: ./path (resolves correctly).
|
|
50
|
+
|
|
51
|
+
Option 2 — Publish shared composite actions to a dedicated standalone repository
|
|
52
|
+
(e.g., org/shared-actions) and reference them with the full path:
|
|
53
|
+
uses: org/shared-actions/path/to-action@main
|
|
54
|
+
|
|
55
|
+
Option 3 — Pass all data that the local action would have computed as inputs to the
|
|
56
|
+
reusable workflow, removing the need for the caller's local action inside the callee.
|
|
57
|
+
fix_code:
|
|
58
|
+
- language: yaml
|
|
59
|
+
label: "Co-locate composite action in the same repo as the reusable workflow"
|
|
60
|
+
code: |
|
|
61
|
+
# Repository: org/shared-workflows
|
|
62
|
+
# Structure:
|
|
63
|
+
# .github/
|
|
64
|
+
# workflows/
|
|
65
|
+
# ci.yml <- reusable workflow
|
|
66
|
+
# actions/
|
|
67
|
+
# shared-lint/
|
|
68
|
+
# action.yml <- composite action lives HERE (same repo)
|
|
69
|
+
|
|
70
|
+
# In org/shared-workflows/.github/workflows/ci.yml:
|
|
71
|
+
on:
|
|
72
|
+
workflow_call:
|
|
73
|
+
inputs:
|
|
74
|
+
source-directory:
|
|
75
|
+
type: string
|
|
76
|
+
required: true
|
|
77
|
+
|
|
78
|
+
jobs:
|
|
79
|
+
lint:
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
steps:
|
|
82
|
+
# Resolves to org/shared-workflows/.github/actions/shared-lint
|
|
83
|
+
- uses: ./.github/actions/shared-lint
|
|
84
|
+
with:
|
|
85
|
+
src-path: ${{ inputs.source-directory }}
|
|
86
|
+
|
|
87
|
+
- language: yaml
|
|
88
|
+
label: "Reference composite action from a standalone shared repo"
|
|
89
|
+
code: |
|
|
90
|
+
# Instead of: uses: ./local-action (broken — resolves to reusable workflow's repo)
|
|
91
|
+
# Use the full repo reference pointing to org/shared-actions:
|
|
92
|
+
jobs:
|
|
93
|
+
deploy:
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
steps:
|
|
96
|
+
- uses: org/shared-actions/deploy@v2
|
|
97
|
+
with:
|
|
98
|
+
environment: ${{ inputs.environment }}
|
|
99
|
+
- uses: org/shared-actions/notify@v2
|
|
100
|
+
with:
|
|
101
|
+
status: ${{ job.status }}
|
|
102
|
+
|
|
103
|
+
prevention:
|
|
104
|
+
- "Keep reusable workflow files and their local composite action dependencies in the same repository"
|
|
105
|
+
- "Publish composite actions shared across many repositories to a dedicated org/shared-actions repo"
|
|
106
|
+
- "Test reusable workflows by calling them from a different repository early in development"
|
|
107
|
+
- "Document which composite actions a reusable workflow depends on and where they must be located"
|
|
108
|
+
docs:
|
|
109
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows"
|
|
110
|
+
label: "GitHub docs — Reusing workflows"
|
|
111
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/creating-actions/about-custom-actions#choosing-a-location-for-your-action"
|
|
112
|
+
label: "GitHub docs — Choosing a location for your action"
|
|
113
|
+
- url: "https://github.com/orgs/community/discussions/17244"
|
|
114
|
+
label: "GitHub Community discussion — local composite action reference from reusable workflow"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
id: runner-environment-136
|
|
2
|
+
title: "actions/github-script require() of third-party npm packages fails with MODULE_NOT_FOUND"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- github-script
|
|
7
|
+
- nodejs
|
|
8
|
+
- require
|
|
9
|
+
- npm
|
|
10
|
+
- module-not-found
|
|
11
|
+
- third-party-package
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Cannot find module'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'MODULE_NOT_FOUND'
|
|
16
|
+
flags: ''
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Error: Cannot find module 'axios'"
|
|
19
|
+
- "Error: Cannot find module 'lodash'"
|
|
20
|
+
- "Error: Cannot find module 'js-yaml'"
|
|
21
|
+
- "Error: Cannot find module 'semver'"
|
|
22
|
+
- "Cannot find module at /home/runner/work/_actions/actions/github-script/v7/lib/async-function.js:1"
|
|
23
|
+
root_cause: |
|
|
24
|
+
The actions/github-script action executes scripts in a sandboxed Node.js runtime that
|
|
25
|
+
only includes packages bundled with the action itself. Built-in parameters available
|
|
26
|
+
in the script context are:
|
|
27
|
+
- github — authenticated Octokit client (@octokit/rest)
|
|
28
|
+
- context — workflow event context
|
|
29
|
+
- core — @actions/core
|
|
30
|
+
- glob — @actions/glob
|
|
31
|
+
- io — @actions/io
|
|
32
|
+
- exec — @actions/exec
|
|
33
|
+
- fetch — Node.js built-in fetch (Node 18+)
|
|
34
|
+
|
|
35
|
+
Any call to require() for packages NOT in this list — such as axios, lodash, js-yaml,
|
|
36
|
+
semver, chalk, or any other npm package — will fail with MODULE_NOT_FOUND because the
|
|
37
|
+
packages are not installed in the runner environment that executes the script.
|
|
38
|
+
|
|
39
|
+
The action does NOT auto-install packages from the repo's package.json or any other
|
|
40
|
+
manifest. Node.js module resolution only searches paths within the bundled action's
|
|
41
|
+
own node_modules directory, which contains only the above built-ins.
|
|
42
|
+
fix: |
|
|
43
|
+
Option 1 (install before use) — Run npm install in a prior step and reference the
|
|
44
|
+
package via full path using RUNNER_TEMP or GITHUB_WORKSPACE:
|
|
45
|
+
- name: Install npm package
|
|
46
|
+
run: npm install <package-name>
|
|
47
|
+
working-directory: ${{ runner.temp }}
|
|
48
|
+
- uses: actions/github-script@v7
|
|
49
|
+
with:
|
|
50
|
+
script: |
|
|
51
|
+
const pkg = require(process.env.RUNNER_TEMP + '/node_modules/<package-name>')
|
|
52
|
+
|
|
53
|
+
Option 2 (use built-ins) — Refactor to use only the built-in parameters. For HTTP
|
|
54
|
+
requests use fetch() instead of axios; for YAML parsing use a run: step with a script
|
|
55
|
+
file.
|
|
56
|
+
|
|
57
|
+
Option 3 (separate Node.js script) — Move complex logic into a .js file in the repo
|
|
58
|
+
and run it with node in a run: step after npm install, giving full package access.
|
|
59
|
+
fix_code:
|
|
60
|
+
- language: yaml
|
|
61
|
+
label: "Install package before github-script and require via RUNNER_TEMP"
|
|
62
|
+
code: |
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v4
|
|
65
|
+
|
|
66
|
+
- name: Install axios for github-script
|
|
67
|
+
run: npm install axios
|
|
68
|
+
working-directory: ${{ runner.temp }}
|
|
69
|
+
|
|
70
|
+
- uses: actions/github-script@v7
|
|
71
|
+
with:
|
|
72
|
+
script: |
|
|
73
|
+
const axios = require(process.env.RUNNER_TEMP + '/node_modules/axios')
|
|
74
|
+
const { data } = await axios.get('https://api.example.com/status')
|
|
75
|
+
core.setOutput('api-status', data.status)
|
|
76
|
+
|
|
77
|
+
- language: yaml
|
|
78
|
+
label: "Replace axios with built-in fetch() (Node 18+, no require needed)"
|
|
79
|
+
code: |
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/github-script@v7
|
|
82
|
+
with:
|
|
83
|
+
script: |
|
|
84
|
+
// fetch is built-in — no require() needed (Node 18+, github-script v7+)
|
|
85
|
+
const response = await fetch('https://api.example.com/status')
|
|
86
|
+
const data = await response.json()
|
|
87
|
+
core.setOutput('api-status', data.status)
|
|
88
|
+
|
|
89
|
+
prevention:
|
|
90
|
+
- "Check the github-script README 'Built-in variables' section before adding require() calls"
|
|
91
|
+
- "Use fetch() (built-in from Node 18+ / github-script v7+) instead of axios for HTTP requests"
|
|
92
|
+
- "For complex logic needing many npm packages, use a separate script file with a run: node step"
|
|
93
|
+
- "Consider caching the npm install step if packages are large or installed frequently"
|
|
94
|
+
docs:
|
|
95
|
+
- url: "https://github.com/actions/github-script?tab=readme-ov-file#built-in-variables"
|
|
96
|
+
label: "actions/github-script — Built-in variables (official README)"
|
|
97
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-github-script-in-a-workflow"
|
|
98
|
+
label: "GitHub docs — Using GitHub Script in a workflow"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
id: silent-failures-071
|
|
2
|
+
title: "${{ env.VAR }} in a step's env: block evaluates to empty string when VAR is a sibling key in the same block"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- env-context
|
|
7
|
+
- expression
|
|
8
|
+
- env-block
|
|
9
|
+
- sibling-reference
|
|
10
|
+
- empty-string
|
|
11
|
+
- configuration
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'env\.\w+\}\}.*\{\{\s*env\.\w+'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "(no error message — step runs successfully but env var is assigned an incorrect partial value)"
|
|
17
|
+
root_cause: |
|
|
18
|
+
When multiple environment variables are defined in the same step (or job) env: block
|
|
19
|
+
and one attempts to compose a value from a sibling key using ${{ env.X }}, the
|
|
20
|
+
reference silently evaluates to an EMPTY STRING.
|
|
21
|
+
|
|
22
|
+
GitHub Actions evaluates all expressions in an env: block BEFORE the step runs, using
|
|
23
|
+
the env context that exists at that point in time. That context includes:
|
|
24
|
+
- Variables defined in the workflow-level env: block
|
|
25
|
+
- Variables defined in the job-level env: block (jobs.<id>.env)
|
|
26
|
+
- Variables added by previous steps via $GITHUB_ENV
|
|
27
|
+
|
|
28
|
+
It does NOT include sibling keys defined elsewhere in the SAME env: block being
|
|
29
|
+
evaluated. The block is evaluated atomically — each value is expanded using only
|
|
30
|
+
the env context from BEFORE the block, not from within it.
|
|
31
|
+
|
|
32
|
+
Example of the mistake:
|
|
33
|
+
env:
|
|
34
|
+
BASE_URL: https://api.example.com # this is fine
|
|
35
|
+
FULL_URL: ${{ env.BASE_URL }}/v2 # SILENTLY wrong: evaluates to "/v2"
|
|
36
|
+
|
|
37
|
+
FULL_URL becomes "/v2" (not "https://api.example.com/v2") because BASE_URL is not
|
|
38
|
+
in the env context at the time the step's env: block expressions are evaluated —
|
|
39
|
+
it is only available AFTER the step starts executing.
|
|
40
|
+
|
|
41
|
+
This is a silent failure: no warning is produced, the step succeeds, and the
|
|
42
|
+
misconfigured variable is silently assigned the wrong value.
|
|
43
|
+
fix: |
|
|
44
|
+
Option 1 (recommended) — Move the "base" variable to the workflow-level or job-level
|
|
45
|
+
env: block so it is already in the env context when the step's env: block is evaluated.
|
|
46
|
+
|
|
47
|
+
Option 2 — Compose the derived value inside the run: step using shell variable
|
|
48
|
+
expansion, which operates at runtime when all variables are already set.
|
|
49
|
+
|
|
50
|
+
Option 3 — Use an expression based on inputs, vars, or secrets contexts which ARE
|
|
51
|
+
fully available during env: block evaluation.
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "Hoist base variable to workflow-level env so it's in context"
|
|
55
|
+
code: |
|
|
56
|
+
# Define BASE_URL at workflow level — it will be in the env context
|
|
57
|
+
env:
|
|
58
|
+
BASE_URL: https://api.example.com
|
|
59
|
+
|
|
60
|
+
jobs:
|
|
61
|
+
deploy:
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
- name: Call API
|
|
65
|
+
run: curl "$FULL_URL"
|
|
66
|
+
env:
|
|
67
|
+
# env.BASE_URL is available here because it's defined at workflow level
|
|
68
|
+
FULL_URL: ${{ env.BASE_URL }}/v2/endpoint
|
|
69
|
+
|
|
70
|
+
- language: yaml
|
|
71
|
+
label: "Compose the value inside run: using shell variable expansion"
|
|
72
|
+
code: |
|
|
73
|
+
steps:
|
|
74
|
+
- name: Call API endpoint
|
|
75
|
+
run: |
|
|
76
|
+
# Shell variable composition works at runtime when BASE_URL is already set
|
|
77
|
+
FULL_URL="${BASE_URL}/v2/endpoint"
|
|
78
|
+
curl "$FULL_URL"
|
|
79
|
+
env:
|
|
80
|
+
BASE_URL: https://api.example.com
|
|
81
|
+
# FULL_URL built in shell — no need to compose in env: block
|
|
82
|
+
|
|
83
|
+
prevention:
|
|
84
|
+
- "Define 'base' env vars at workflow or job level when they need to be referenced by step-level env: blocks"
|
|
85
|
+
- "Use shell variable composition inside run: steps rather than ${{ env.X }} in the same env: block"
|
|
86
|
+
- "Verify composed env var values by printing them with echo before use in critical steps"
|
|
87
|
+
- "Review GitHub Actions context availability documentation when authoring env: blocks"
|
|
88
|
+
docs:
|
|
89
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/variables#using-the-env-context-to-access-environment-variable-values"
|
|
90
|
+
label: "GitHub docs — Using the env context to access environment variable values"
|
|
91
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#context-availability"
|
|
92
|
+
label: "GitHub docs — Context availability table"
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
id: triggers-051
|
|
2
|
+
title: "on: create event fires for both branch and tag creation — must filter with github.ref_type"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- create-event
|
|
7
|
+
- delete-event
|
|
8
|
+
- ref-type
|
|
9
|
+
- tag
|
|
10
|
+
- branch
|
|
11
|
+
- trigger-filter
|
|
12
|
+
- release
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'on:\s*[\r\n]+\s+create:'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
error_messages:
|
|
17
|
+
- "(no error message — workflow triggers unexpectedly on branch creation, not just tag creation)"
|
|
18
|
+
root_cause: |
|
|
19
|
+
The on: create event triggers whenever ANY ref (branch OR tag) is created in the
|
|
20
|
+
repository. There is no built-in filter to restrict it to only tag creation or only
|
|
21
|
+
branch creation.
|
|
22
|
+
|
|
23
|
+
Many developers use on: create expecting to run release or version-publish workflows
|
|
24
|
+
only when a version tag is pushed, but the workflow also fires for:
|
|
25
|
+
- Feature branches created via the GitHub UI or API
|
|
26
|
+
- Dependabot version update branches (dependabot/npm_and_yarn/...)
|
|
27
|
+
- Automated branches created by bots or scripts
|
|
28
|
+
- PR branches created by tooling
|
|
29
|
+
- Any branch creation event from any actor
|
|
30
|
+
|
|
31
|
+
The github.ref_type context variable ('branch' or 'tag') is available in the run
|
|
32
|
+
context but on: create has no built-in filter for it — an explicit if: condition
|
|
33
|
+
on the job or step must be used to restrict execution.
|
|
34
|
+
|
|
35
|
+
The same behavior applies to on: delete — it fires for both branch and tag deletion.
|
|
36
|
+
fix: |
|
|
37
|
+
Add an if: condition at the job level (or step level) to filter by github.ref_type:
|
|
38
|
+
|
|
39
|
+
jobs:
|
|
40
|
+
release:
|
|
41
|
+
if: github.ref_type == 'tag' # restrict to tag creation only
|
|
42
|
+
|
|
43
|
+
Alternative: Use on: push with a tags: filter instead of on: create.
|
|
44
|
+
The push event with tags: filter is more explicit, supports glob patterns,
|
|
45
|
+
and does not fire for branch operations at all.
|
|
46
|
+
fix_code:
|
|
47
|
+
- language: yaml
|
|
48
|
+
label: "Filter on: create to tags only using ref_type job condition"
|
|
49
|
+
code: |
|
|
50
|
+
on:
|
|
51
|
+
create:
|
|
52
|
+
|
|
53
|
+
jobs:
|
|
54
|
+
release:
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
# Only run for tag creation (e.g., v1.2.3), not branch creation
|
|
57
|
+
if: github.ref_type == 'tag'
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/checkout@v4
|
|
60
|
+
- name: Build and publish release
|
|
61
|
+
run: echo "Publishing ${{ github.ref_name }}"
|
|
62
|
+
|
|
63
|
+
- language: yaml
|
|
64
|
+
label: "Preferred: use on: push with tags filter for tag-triggered workflows"
|
|
65
|
+
code: |
|
|
66
|
+
# More explicit and common pattern for release workflows
|
|
67
|
+
on:
|
|
68
|
+
push:
|
|
69
|
+
tags:
|
|
70
|
+
- 'v*.*.*' # triggers on v1.0.0, v2.3.1, etc.
|
|
71
|
+
# - 'v[0-9]+.*' # alternative glob
|
|
72
|
+
|
|
73
|
+
jobs:
|
|
74
|
+
release:
|
|
75
|
+
runs-on: ubuntu-latest
|
|
76
|
+
steps:
|
|
77
|
+
- uses: actions/checkout@v4
|
|
78
|
+
- name: Build and publish release
|
|
79
|
+
run: echo "Publishing ${{ github.ref_name }}"
|
|
80
|
+
|
|
81
|
+
- language: yaml
|
|
82
|
+
label: "Filter on: delete to branch deletion only (e.g., branch cleanup)"
|
|
83
|
+
code: |
|
|
84
|
+
on:
|
|
85
|
+
delete:
|
|
86
|
+
|
|
87
|
+
jobs:
|
|
88
|
+
cleanup:
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
# Only run for branch deletion, not tag deletion
|
|
91
|
+
if: github.ref_type == 'branch'
|
|
92
|
+
steps:
|
|
93
|
+
- name: Cleanup branch resources
|
|
94
|
+
run: echo "Cleaning up resources for ${{ github.ref_name }}"
|
|
95
|
+
|
|
96
|
+
prevention:
|
|
97
|
+
- "Always add if: github.ref_type == 'tag' when using on: create for release/publish workflows"
|
|
98
|
+
- "Prefer on: push with tags: filter for tag-triggered release workflows — it's more explicit"
|
|
99
|
+
- "Add if: github.ref_type == 'branch' when using on: delete for branch cleanup automation"
|
|
100
|
+
- "Test create/delete workflows by creating a feature branch to verify it does NOT trigger unexpectedly"
|
|
101
|
+
- "Review all on: create workflows in the repository after adding Dependabot — it creates many branches"
|
|
102
|
+
docs:
|
|
103
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#create"
|
|
104
|
+
label: "GitHub docs — create event"
|
|
105
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#delete"
|
|
106
|
+
label: "GitHub docs — delete event"
|
|
107
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#github-context"
|
|
108
|
+
label: "GitHub docs — github context (ref_type field)"
|
|
@@ -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