@htekdev/actions-debugger 1.0.80 → 1.0.82
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-lookup-only-no-restore-silent.yml +88 -0
- package/errors/caching-artifacts/upload-artifact-rerun-run-id-collision.yml +67 -0
- package/errors/concurrency-timing/workflow-level-vs-job-level-concurrency-scope.yml +92 -0
- package/errors/known-unsolved/org-required-workflow-no-per-repo-override.yml +88 -0
- package/errors/runner-environment/larger-runner-labels-require-paid-plan.yml +73 -0
- package/errors/runner-environment/macos-bash-32-no-bash4-features.yml +110 -0
- package/errors/runner-environment/pip-externally-managed-environment-pep668.yml +83 -0
- package/errors/runner-environment/ubuntu-24-ruby-not-preinstalled.yml +74 -0
- package/errors/runner-environment/windows-shell-powershell-is-ps5-not-ps7.yml +85 -0
- package/errors/silent-failures/github-event-inputs-undefined-in-workflow-call.yml +102 -0
- package/errors/silent-failures/if-failure-not-triggered-on-cancellation.yml +96 -0
- package/errors/silent-failures/job-outputs-block-missing-needs-always-empty.yml +85 -0
- package/errors/triggers/push-branches-filter-bypassed-by-tag-push.yml +83 -0
- package/errors/triggers/release-created-fires-on-draft.yml +75 -0
- package/errors/triggers/workflow-dispatch-choice-input-api-no-validation.yml +98 -0
- package/errors/yaml-syntax/secrets-in-workflow-level-env-block-rejected.yml +91 -0
- package/package.json +1 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
id: runner-environment-147
|
|
2
|
+
title: "Windows runner 'shell: powershell' invokes PowerShell 5 not PowerShell 7"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- windows
|
|
7
|
+
- powershell
|
|
8
|
+
- pwsh
|
|
9
|
+
- shell
|
|
10
|
+
- powershell-version
|
|
11
|
+
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'The term .{0,20} is not recognized as the name of a cmdlet'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'Unexpected token.*\?\.'
|
|
16
|
+
flags: i
|
|
17
|
+
- regex: 'parameter cannot be found.*Parallel'
|
|
18
|
+
flags: i
|
|
19
|
+
|
|
20
|
+
error_messages:
|
|
21
|
+
- "The term '??' is not recognized as the name of a cmdlet, function, script file, or operable program."
|
|
22
|
+
- "Unexpected token '?.' in expression or statement."
|
|
23
|
+
- "ForEach-Object: A parameter cannot be found that matches parameter name 'Parallel'."
|
|
24
|
+
|
|
25
|
+
root_cause: |
|
|
26
|
+
On Windows GitHub-hosted runners, explicitly setting `shell: powershell` in a
|
|
27
|
+
workflow step invokes `powershell.exe` — Windows PowerShell 5.1 — NOT the modern
|
|
28
|
+
PowerShell 7.x (pwsh).
|
|
29
|
+
|
|
30
|
+
Windows PowerShell 5.1 is a separate executable that lacks numerous features
|
|
31
|
+
introduced in PowerShell 6.0 (Core) and later:
|
|
32
|
+
- Null-coalescing operator: `$a ?? $b` (requires PS 7.0+)
|
|
33
|
+
- Null-coalescing assignment: `$a ??= 'default'` (requires PS 7.0+)
|
|
34
|
+
- Null-conditional member access: `$obj?.Property` (requires PS 7.0+)
|
|
35
|
+
- Ternary operator: `$x ? $y : $z` (requires PS 7.0+)
|
|
36
|
+
- ForEach-Object -Parallel (requires PS 7.0+)
|
|
37
|
+
- Pipeline chain operators: `&&`, `||` (requires PS 7.0+)
|
|
38
|
+
|
|
39
|
+
The default shell on Windows runners (when no `shell:` key is specified) is `pwsh`
|
|
40
|
+
(PowerShell 7). This means workflows that omit `shell:` work fine, but any step that
|
|
41
|
+
explicitly adds `shell: powershell` regresses to PowerShell 5.1.
|
|
42
|
+
|
|
43
|
+
Developers often set `shell: powershell` when they mean "use PowerShell" without
|
|
44
|
+
knowing the distinction between Windows PowerShell and PowerShell 7.
|
|
45
|
+
|
|
46
|
+
fix: |
|
|
47
|
+
Replace `shell: powershell` with `shell: pwsh` to use PowerShell 7.
|
|
48
|
+
|
|
49
|
+
If the workflow requires PowerShell 5.1 compatibility (rare), audit the script
|
|
50
|
+
and remove PS7-only syntax. However, in almost all cases the intent is PowerShell 7.
|
|
51
|
+
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "Use pwsh for PowerShell 7 (fix)"
|
|
55
|
+
code: |
|
|
56
|
+
# Before (broken on modern PS syntax):
|
|
57
|
+
# - run: $value = $null ?? "default"
|
|
58
|
+
# shell: powershell
|
|
59
|
+
|
|
60
|
+
# After (correct):
|
|
61
|
+
- run: $value = $null ?? "default"
|
|
62
|
+
shell: pwsh
|
|
63
|
+
- language: yaml
|
|
64
|
+
label: "Set pwsh as default shell for all Windows steps"
|
|
65
|
+
code: |
|
|
66
|
+
defaults:
|
|
67
|
+
run:
|
|
68
|
+
shell: pwsh
|
|
69
|
+
|
|
70
|
+
jobs:
|
|
71
|
+
build:
|
|
72
|
+
runs-on: windows-latest
|
|
73
|
+
steps:
|
|
74
|
+
- run: $value = $null ?? "default" # uses pwsh by default
|
|
75
|
+
|
|
76
|
+
prevention:
|
|
77
|
+
- 'Use `shell: pwsh` (not `shell: powershell`) in any step using PowerShell 6+ syntax.'
|
|
78
|
+
- 'Set `defaults.run.shell: pwsh` at workflow level to apply PowerShell 7 globally on Windows.'
|
|
79
|
+
- 'Use actionlint — it does not currently distinguish PS5 vs PS7 but code review should catch explicit `shell: powershell`.'
|
|
80
|
+
|
|
81
|
+
docs:
|
|
82
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-syntax-for-github-actions#jobsjob_idstepsshell'
|
|
83
|
+
label: 'Workflow syntax: shell — GitHub Docs'
|
|
84
|
+
- url: 'https://learn.microsoft.com/en-us/powershell/scripting/whats-new/differences-from-windows-powershell'
|
|
85
|
+
label: 'Differences between Windows PowerShell 5.1 and PowerShell 7 — Microsoft Docs'
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
id: silent-failures-077
|
|
2
|
+
title: '`github.event.inputs` is undefined (null) when workflow triggered via `workflow_call`'
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow-call
|
|
7
|
+
- workflow-dispatch
|
|
8
|
+
- inputs
|
|
9
|
+
- github-event-inputs
|
|
10
|
+
- reusable-workflow
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'github\.event\.inputs\.'
|
|
13
|
+
flags: 'g'
|
|
14
|
+
- regex: 'on:\s*\n\s+workflow_call:'
|
|
15
|
+
flags: 'im'
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Expression github.event.inputs.environment evaluated to ''"
|
|
18
|
+
- 'github.event.inputs is null'
|
|
19
|
+
- 'unexpected value '''''
|
|
20
|
+
root_cause: |
|
|
21
|
+
When a workflow supports both `workflow_dispatch` and `workflow_call` triggers,
|
|
22
|
+
developers often use `github.event.inputs.param` to read input values.
|
|
23
|
+
This works for `workflow_dispatch` but silently fails for `workflow_call`:
|
|
24
|
+
|
|
25
|
+
- `workflow_dispatch`: inputs are surfaced at `github.event.inputs.*`
|
|
26
|
+
(always strings regardless of declared type)
|
|
27
|
+
- `workflow_call`: inputs are NOT placed in `github.event.inputs`; they are
|
|
28
|
+
only available via the `inputs` context (`inputs.param`)
|
|
29
|
+
|
|
30
|
+
When a reusable workflow is invoked via `workflow_call`, `github.event.inputs`
|
|
31
|
+
is an empty object — referencing `github.event.inputs.param` evaluates to an
|
|
32
|
+
empty string `''`, not an error. The workflow continues running with silently
|
|
33
|
+
missing parameter values, producing incorrect behavior rather than a visible
|
|
34
|
+
failure.
|
|
35
|
+
|
|
36
|
+
The `inputs` context (without `github.event.`) is the correct way to read
|
|
37
|
+
inputs in both scenarios, as it is populated for both `workflow_dispatch` and
|
|
38
|
+
`workflow_call`.
|
|
39
|
+
fix: |
|
|
40
|
+
Replace all `github.event.inputs.*` references with `inputs.*` — the `inputs`
|
|
41
|
+
context works correctly for both `workflow_dispatch` and `workflow_call` events.
|
|
42
|
+
fix_code:
|
|
43
|
+
- language: yaml
|
|
44
|
+
label: 'Use inputs context instead of github.event.inputs'
|
|
45
|
+
code: |
|
|
46
|
+
on:
|
|
47
|
+
workflow_dispatch:
|
|
48
|
+
inputs:
|
|
49
|
+
environment:
|
|
50
|
+
type: string
|
|
51
|
+
required: true
|
|
52
|
+
workflow_call:
|
|
53
|
+
inputs:
|
|
54
|
+
environment:
|
|
55
|
+
type: string
|
|
56
|
+
required: true
|
|
57
|
+
|
|
58
|
+
jobs:
|
|
59
|
+
deploy:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
steps:
|
|
62
|
+
# BAD: github.event.inputs is empty/null in workflow_call context
|
|
63
|
+
- run: echo "Deploying to ${{ github.event.inputs.environment }}"
|
|
64
|
+
|
|
65
|
+
# GOOD: inputs context works for both workflow_dispatch and workflow_call
|
|
66
|
+
- run: echo "Deploying to ${{ inputs.environment }}"
|
|
67
|
+
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: 'Dual-trigger workflow using inputs context correctly'
|
|
70
|
+
code: |
|
|
71
|
+
on:
|
|
72
|
+
workflow_dispatch:
|
|
73
|
+
inputs:
|
|
74
|
+
dry_run:
|
|
75
|
+
type: boolean
|
|
76
|
+
default: false
|
|
77
|
+
workflow_call:
|
|
78
|
+
inputs:
|
|
79
|
+
dry_run:
|
|
80
|
+
type: boolean
|
|
81
|
+
default: false
|
|
82
|
+
|
|
83
|
+
jobs:
|
|
84
|
+
release:
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/checkout@v4
|
|
88
|
+
# inputs.dry_run works for both dispatch and call
|
|
89
|
+
- if: '!inputs.dry_run'
|
|
90
|
+
run: ./publish.sh
|
|
91
|
+
prevention:
|
|
92
|
+
- 'Always use the `inputs` context (not `github.event.inputs`) when reading inputs in workflows that support both `workflow_dispatch` and `workflow_call`'
|
|
93
|
+
- 'Grep your workflow files for `github.event.inputs` and replace with `inputs` before adding a `workflow_call` trigger'
|
|
94
|
+
- '`github.event.inputs` values are always strings; `inputs` respects the declared type (boolean, number, string, choice)'
|
|
95
|
+
- 'Test reusable workflows by calling them from another workflow, not just via the UI dispatch button'
|
|
96
|
+
docs:
|
|
97
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_call'
|
|
98
|
+
label: 'workflow_call event — GitHub Docs'
|
|
99
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_dispatch'
|
|
100
|
+
label: 'workflow_dispatch event — GitHub Docs'
|
|
101
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#inputs-context'
|
|
102
|
+
label: 'inputs context — GitHub Docs'
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
id: silent-failures-075
|
|
2
|
+
title: "if: failure() notification job is skipped when workflow is manually cancelled"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- if-condition
|
|
7
|
+
- failure
|
|
8
|
+
- cancelled
|
|
9
|
+
- always
|
|
10
|
+
- notification
|
|
11
|
+
- cleanup
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'if:\s*failure\(\)'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'Skipping.*cancelled'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Skipping: The job was skipped because one or more of its needs jobs was cancelled."
|
|
19
|
+
root_cause: |
|
|
20
|
+
When a workflow is manually cancelled (via the GitHub UI "Cancel run" button or
|
|
21
|
+
the REST API), in-progress jobs receive a cancellation signal. Jobs that have not
|
|
22
|
+
yet started are given the conclusion "cancelled". Jobs with if: failure() are
|
|
23
|
+
only evaluated against the "failure" conclusion — "cancelled" is a distinct
|
|
24
|
+
conclusion value. Because cancelled != failure, the condition evaluates to false
|
|
25
|
+
and the notification or cleanup job is silently skipped.
|
|
26
|
+
|
|
27
|
+
This catches teams by surprise when they add an alerting job intended to fire
|
|
28
|
+
whenever CI does not succeed:
|
|
29
|
+
|
|
30
|
+
notify-on-failure:
|
|
31
|
+
needs: build
|
|
32
|
+
if: failure()
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- name: Send Slack alert
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
When an operator cancels a run mid-flight, the build job is marked cancelled,
|
|
39
|
+
not failed. The notify job evaluates if: failure() → false and is skipped with
|
|
40
|
+
no Slack message. The on-call team receives no signal that a run was interrupted.
|
|
41
|
+
|
|
42
|
+
The failure() function returns true only when at least one needed job has a
|
|
43
|
+
"failure" conclusion. It does NOT return true for "cancelled" conclusions.
|
|
44
|
+
fix: |
|
|
45
|
+
Use if: failure() || cancelled() to catch both failure and cancellation, or use
|
|
46
|
+
if: always() combined with a conditional check on the job's outcome inside the
|
|
47
|
+
step if you only want to act on non-success outcomes.
|
|
48
|
+
fix_code:
|
|
49
|
+
- language: yaml
|
|
50
|
+
label: "Catch both failure and cancellation in a notification job"
|
|
51
|
+
code: |
|
|
52
|
+
jobs:
|
|
53
|
+
build:
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- run: make build
|
|
57
|
+
|
|
58
|
+
notify-on-failure:
|
|
59
|
+
needs: build
|
|
60
|
+
# failure() catches job failures; cancelled() catches manual cancellation
|
|
61
|
+
if: failure() || cancelled()
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
- name: Send failure or cancellation alert
|
|
65
|
+
run: |
|
|
66
|
+
echo "Build concluded: ${{ needs.build.result }}"
|
|
67
|
+
# Send Slack/PagerDuty notification here
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: "Use always() and gate on outcome inside the step for fine-grained control"
|
|
70
|
+
code: |
|
|
71
|
+
jobs:
|
|
72
|
+
build:
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
steps:
|
|
75
|
+
- run: make build
|
|
76
|
+
|
|
77
|
+
notify:
|
|
78
|
+
needs: build
|
|
79
|
+
if: always()
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
steps:
|
|
82
|
+
- name: Notify only on non-success outcomes
|
|
83
|
+
if: needs.build.result != 'success'
|
|
84
|
+
run: |
|
|
85
|
+
echo "Build result: ${{ needs.build.result }}"
|
|
86
|
+
# Handles: failure, cancelled, skipped
|
|
87
|
+
prevention:
|
|
88
|
+
- "Use if: failure() || cancelled() instead of if: failure() for any alert, notification, or cleanup job that must run whenever CI does not succeed."
|
|
89
|
+
- "Prefer if: always() when the downstream job should run regardless of upstream outcome (e.g., always upload test reports)."
|
|
90
|
+
- "Audit all notification and cleanup jobs after cancellation to confirm they fired as expected — cancelled runs don't generate failure alerts by default."
|
|
91
|
+
- "The cancelled() function returns true when the workflow or job has been cancelled; combine with failure() using || for comprehensive coverage."
|
|
92
|
+
docs:
|
|
93
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-conditions-to-control-job-execution"
|
|
94
|
+
label: "GitHub Actions: Using conditions to control job execution"
|
|
95
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-conditions-to-control-job-execution#available-status-check-functions"
|
|
96
|
+
label: "GitHub Actions: Available status check functions — failure(), cancelled(), always()"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
id: silent-failures-076
|
|
2
|
+
title: "Job outputs: block missing — needs.job.outputs.key is always empty string in downstream jobs"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- job-outputs
|
|
7
|
+
- needs-context
|
|
8
|
+
- GITHUB_OUTPUT
|
|
9
|
+
- outputs-block
|
|
10
|
+
- empty-string
|
|
11
|
+
- downstream-jobs
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'needs\.[a-z_][a-z0-9_-]*\.outputs\.[a-z_][a-z0-9_-]*'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "needs.deploy.outputs.artifact_url is always empty string"
|
|
17
|
+
- "needs.build.outputs.sha: empty output in downstream job"
|
|
18
|
+
root_cause: |
|
|
19
|
+
Writing to $GITHUB_OUTPUT inside a step only makes the value available within that job
|
|
20
|
+
via steps.<step_id>.outputs.<key>. To expose an output to downstream jobs via
|
|
21
|
+
needs.<job>.outputs.<key>, the job must explicitly declare an outputs: block that maps
|
|
22
|
+
key names to step output expressions using ${{ steps.<id>.outputs.<key> }}.
|
|
23
|
+
|
|
24
|
+
Without the job-level outputs: mapping, $GITHUB_OUTPUT writes are silently ignored by
|
|
25
|
+
downstream consumers — needs.<job>.outputs.<key> evaluates to an empty string with no
|
|
26
|
+
warning, error, or annotation. The step that wrote to $GITHUB_OUTPUT exits with code 0
|
|
27
|
+
and no indication of the problem.
|
|
28
|
+
|
|
29
|
+
This two-step mechanism is required: the step writes to $GITHUB_OUTPUT (job-internal),
|
|
30
|
+
and the job's outputs: block re-exports selected values to the workflow's needs graph.
|
|
31
|
+
fix: |
|
|
32
|
+
Add an outputs: block at the job level (as a sibling of runs-on:, steps:, etc.)
|
|
33
|
+
that maps the desired key names to the corresponding step output expressions.
|
|
34
|
+
Every key you want accessible via needs.<job>.outputs.<key> must appear in this block.
|
|
35
|
+
fix_code:
|
|
36
|
+
- language: yaml
|
|
37
|
+
label: "Correct: job outputs: block declares which step outputs to expose"
|
|
38
|
+
code: |
|
|
39
|
+
jobs:
|
|
40
|
+
build:
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
outputs:
|
|
43
|
+
artifact-version: ${{ steps.version.outputs.value }}
|
|
44
|
+
build-sha: ${{ steps.hash.outputs.sha }}
|
|
45
|
+
steps:
|
|
46
|
+
- name: Compute version
|
|
47
|
+
id: version
|
|
48
|
+
run: echo "value=1.2.3" >> $GITHUB_OUTPUT
|
|
49
|
+
- name: Compute hash
|
|
50
|
+
id: hash
|
|
51
|
+
run: echo "sha=$(sha256sum dist/app | cut -d' ' -f1)" >> $GITHUB_OUTPUT
|
|
52
|
+
|
|
53
|
+
deploy:
|
|
54
|
+
needs: build
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
steps:
|
|
57
|
+
- run: echo "Deploying ${{ needs.build.outputs.artifact-version }}"
|
|
58
|
+
- language: yaml
|
|
59
|
+
label: "Wrong: outputs: block absent — needs.build.outputs.* is always empty"
|
|
60
|
+
code: |
|
|
61
|
+
jobs:
|
|
62
|
+
build:
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
# No outputs: block — step writes are invisible to downstream jobs
|
|
65
|
+
steps:
|
|
66
|
+
- name: Compute version
|
|
67
|
+
id: version
|
|
68
|
+
run: echo "value=1.2.3" >> $GITHUB_OUTPUT
|
|
69
|
+
|
|
70
|
+
deploy:
|
|
71
|
+
needs: build
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- run: echo "${{ needs.build.outputs.artifact-version }}" # Always ''
|
|
75
|
+
prevention:
|
|
76
|
+
- "Any job that produces values for downstream jobs MUST have a matching outputs: block"
|
|
77
|
+
- "Use actionlint — it warns when needs.<job>.outputs.<key> references an undeclared key"
|
|
78
|
+
- "Debug by printing ${{ toJSON(needs) }} in a downstream step to see all available outputs"
|
|
79
|
+
- "Note: steps.<id>.outputs.<key> within the same job does NOT require an outputs: block"
|
|
80
|
+
- "Composite action outputs and reusable workflow outputs have separate but analogous declaration requirements"
|
|
81
|
+
docs:
|
|
82
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs"
|
|
83
|
+
label: "GitHub Docs: Passing information between jobs"
|
|
84
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idoutputs"
|
|
85
|
+
label: "GitHub Docs: jobs.<job_id>.outputs syntax"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
id: triggers-055
|
|
2
|
+
title: "branches: filter does not apply to tag pushes — tag push always triggers unless tags-ignore is set"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- push
|
|
7
|
+
- branches-filter
|
|
8
|
+
- tags
|
|
9
|
+
- tag-push
|
|
10
|
+
- tags-ignore
|
|
11
|
+
- ref-type
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'on:\s*\n\s+push:\s*\n\s+branches:'
|
|
14
|
+
flags: 'm'
|
|
15
|
+
- regex: 'Triggered by refs/tags/'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Run triggered by push to refs/tags/v1.0.0 (unexpected — only branches: [main] was set)"
|
|
19
|
+
root_cause: |
|
|
20
|
+
The branches: filter under on.push: only evaluates branch refs (refs/heads/*). A push
|
|
21
|
+
to a tag ref (refs/tags/*) is an entirely separate ref type that the branches: filter
|
|
22
|
+
has no authority to gate. If no tags: or tags-ignore: filter is specified under
|
|
23
|
+
on.push:, ALL tag pushes pass through unconditionally.
|
|
24
|
+
|
|
25
|
+
This causes workflows intended to run "only on pushes to main" to also run on every
|
|
26
|
+
release tag, annotated tag, and automated tag created by CI pipelines.
|
|
27
|
+
|
|
28
|
+
Branch and tag filters are independent dimensions of the push event:
|
|
29
|
+
branches: / branches-ignore: — applies ONLY to branch refs (refs/heads/*)
|
|
30
|
+
tags: / tags-ignore: — applies ONLY to tag refs (refs/tags/*)
|
|
31
|
+
|
|
32
|
+
If a filter type is absent, all refs of that type pass through.
|
|
33
|
+
Setting branches: [main] does NOT automatically exclude tags.
|
|
34
|
+
|
|
35
|
+
The same mechanism applies in reverse: a tags: filter does not affect branch pushes.
|
|
36
|
+
And the paths: filter is also bypassed by tag pushes (see push-paths-filter-bypassed-by-tag).
|
|
37
|
+
fix: |
|
|
38
|
+
To restrict the workflow to branch pushes only, add a tags-ignore: ['**'] pattern
|
|
39
|
+
to exclude all tag pushes. Alternatively, add an if: condition to check github.ref_type.
|
|
40
|
+
fix_code:
|
|
41
|
+
- language: yaml
|
|
42
|
+
label: "Option 1: add tags-ignore to explicitly exclude all tag pushes"
|
|
43
|
+
code: |
|
|
44
|
+
on:
|
|
45
|
+
push:
|
|
46
|
+
branches:
|
|
47
|
+
- main
|
|
48
|
+
- 'release/**'
|
|
49
|
+
tags-ignore:
|
|
50
|
+
- '**' # Exclude all tag pushes from triggering this workflow
|
|
51
|
+
- language: yaml
|
|
52
|
+
label: "Option 2: use if condition on the job to check ref_type"
|
|
53
|
+
code: |
|
|
54
|
+
on:
|
|
55
|
+
push:
|
|
56
|
+
branches:
|
|
57
|
+
- main
|
|
58
|
+
|
|
59
|
+
jobs:
|
|
60
|
+
build:
|
|
61
|
+
if: github.ref_type == 'branch' # Skip if triggered by a tag push
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
- run: echo "Only runs on branch pushes"
|
|
65
|
+
- language: yaml
|
|
66
|
+
label: "Option 3: explicitly allow specific tags alongside branches"
|
|
67
|
+
code: |
|
|
68
|
+
on:
|
|
69
|
+
push:
|
|
70
|
+
branches:
|
|
71
|
+
- main
|
|
72
|
+
tags:
|
|
73
|
+
- 'v[0-9]+.[0-9]+.[0-9]+' # Only semver release tags; other tags excluded
|
|
74
|
+
prevention:
|
|
75
|
+
- "Whenever you set branches:, also consider whether you need tags-ignore: ['**'] to exclude tag pushes"
|
|
76
|
+
- "Check if automated tooling (release-please, semantic-release, etc.) creates tags in your repo — they will trigger this workflow"
|
|
77
|
+
- "The same principle applies to paths: filter — it also does not block tag pushes"
|
|
78
|
+
- "Remember the rule: absent filter = all refs of that type pass through"
|
|
79
|
+
docs:
|
|
80
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onpushbranchestagsbranches-ignoretags-ignore"
|
|
81
|
+
label: "GitHub Docs: push event branches/tags filter syntax"
|
|
82
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push"
|
|
83
|
+
label: "GitHub Docs: Push event triggers"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
id: triggers-056
|
|
2
|
+
title: '`on.release: types: [created]` fires when a draft release is first saved'
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- release
|
|
7
|
+
- triggers
|
|
8
|
+
- draft
|
|
9
|
+
- created
|
|
10
|
+
- published
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'types:\s*\[?created\]?'
|
|
13
|
+
flags: 'i'
|
|
14
|
+
- regex: 'on:\s*\n\s+release:'
|
|
15
|
+
flags: 'im'
|
|
16
|
+
error_messages:
|
|
17
|
+
- 'Triggered by: release'
|
|
18
|
+
- 'github.event.action: created'
|
|
19
|
+
- 'github.event.release.draft: true'
|
|
20
|
+
root_cause: |
|
|
21
|
+
The `release` event with `types: [created]` fires when ANY release record is
|
|
22
|
+
first created in GitHub — including draft releases. When a developer clicks
|
|
23
|
+
"Save draft" to prepare release notes before publishing, GitHub fires the
|
|
24
|
+
`created` event immediately. This causes deployment or publish workflows to
|
|
25
|
+
run against an incomplete, unpublished draft.
|
|
26
|
+
|
|
27
|
+
The `created` type maps to the moment the release row is inserted in GitHub's
|
|
28
|
+
database, regardless of draft or published state. Many developers assume
|
|
29
|
+
`created` means "publicly released" because that mirrors the UI action of
|
|
30
|
+
clicking "Publish release", but the event fires earlier.
|
|
31
|
+
|
|
32
|
+
The correct type for "a release is now publicly visible" is `published`, which
|
|
33
|
+
fires only when the release transitions from draft (or pre-release) to a
|
|
34
|
+
published, non-draft release.
|
|
35
|
+
fix: |
|
|
36
|
+
Use `types: [published]` for workflows that should only run when a release
|
|
37
|
+
becomes publicly available. Add an `if:` guard as a defensive layer when
|
|
38
|
+
using `created`.
|
|
39
|
+
fix_code:
|
|
40
|
+
- language: yaml
|
|
41
|
+
label: 'Use published instead of created for deploy workflows'
|
|
42
|
+
code: |
|
|
43
|
+
# BAD: fires on draft creation too
|
|
44
|
+
on:
|
|
45
|
+
release:
|
|
46
|
+
types: [created]
|
|
47
|
+
|
|
48
|
+
# GOOD: fires only when release becomes publicly published
|
|
49
|
+
on:
|
|
50
|
+
release:
|
|
51
|
+
types: [published]
|
|
52
|
+
- language: yaml
|
|
53
|
+
label: 'Defensive if: guard when created type is required'
|
|
54
|
+
code: |
|
|
55
|
+
on:
|
|
56
|
+
release:
|
|
57
|
+
types: [created]
|
|
58
|
+
jobs:
|
|
59
|
+
deploy:
|
|
60
|
+
# Skip execution when triggered by a draft save
|
|
61
|
+
if: '!github.event.release.draft'
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v4
|
|
65
|
+
- run: ./deploy.sh
|
|
66
|
+
prevention:
|
|
67
|
+
- 'Prefer `types: [published]` for deployment workflows to avoid triggering on draft saves'
|
|
68
|
+
- 'Note: `published` also fires when a pre-release is promoted to a full release — guard with `!github.event.release.prerelease` if needed'
|
|
69
|
+
- 'Add `if: ''!github.event.release.draft''` as an explicit guard when using the `created` type'
|
|
70
|
+
- 'Test your release workflow by creating a draft release and verifying it does or does not trigger as expected'
|
|
71
|
+
docs:
|
|
72
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#release'
|
|
73
|
+
label: 'release event — GitHub Docs'
|
|
74
|
+
- url: 'https://docs.github.com/en/rest/releases/releases#create-a-release'
|
|
75
|
+
label: 'Create a release REST API — GitHub Docs'
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
id: triggers-054
|
|
2
|
+
title: "workflow_dispatch choice input not validated when triggered via REST API — invalid values silently accepted"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow-dispatch
|
|
7
|
+
- choice-input
|
|
8
|
+
- rest-api
|
|
9
|
+
- inputs
|
|
10
|
+
- validation
|
|
11
|
+
- gh-cli
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'type:\s*choice'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'inputs\.\w+.*choice'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Workflow ran with input value not in declared choices list"
|
|
19
|
+
root_cause: |
|
|
20
|
+
The type: choice input in on.workflow_dispatch.inputs renders a dropdown
|
|
21
|
+
selector in the GitHub Actions UI and prevents users from submitting values
|
|
22
|
+
outside the declared options list. This validation is enforced entirely
|
|
23
|
+
client-side in the browser.
|
|
24
|
+
|
|
25
|
+
When a workflow is triggered via the GitHub REST API
|
|
26
|
+
(POST /repos/{owner}/{repo}/actions/workflows/{id}/dispatches) or the GitHub
|
|
27
|
+
CLI (gh workflow run --field key=value), GitHub does NOT validate that the
|
|
28
|
+
supplied value matches any of the declared choices. Any arbitrary string is
|
|
29
|
+
accepted and forwarded as-is to the workflow.
|
|
30
|
+
|
|
31
|
+
A workflow dispatched with inputs: { environment: "PROD" } when the declared
|
|
32
|
+
choices are ["prod", "staging", "dev"] will run with inputs.environment == "PROD".
|
|
33
|
+
Every if: condition that compares against the lower-case variants evaluates to
|
|
34
|
+
false, all deployment branches are skipped, and the workflow exits successfully
|
|
35
|
+
with no indication that the value was invalid. The mistyped run is invisible
|
|
36
|
+
unless logs are inspected manually.
|
|
37
|
+
fix: |
|
|
38
|
+
Add an explicit allowlist validation step at the top of the workflow that checks
|
|
39
|
+
the input value and exits with a non-zero status and a descriptive error message
|
|
40
|
+
if the value is not in the expected set. This makes API-triggered runs fail fast
|
|
41
|
+
with a clear error rather than silently no-oping.
|
|
42
|
+
fix_code:
|
|
43
|
+
- language: yaml
|
|
44
|
+
label: "Validate choice input with an allowlist check"
|
|
45
|
+
code: |
|
|
46
|
+
on:
|
|
47
|
+
workflow_dispatch:
|
|
48
|
+
inputs:
|
|
49
|
+
environment:
|
|
50
|
+
type: choice
|
|
51
|
+
description: Target deployment environment
|
|
52
|
+
options:
|
|
53
|
+
- prod
|
|
54
|
+
- staging
|
|
55
|
+
- dev
|
|
56
|
+
|
|
57
|
+
jobs:
|
|
58
|
+
deploy:
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
steps:
|
|
61
|
+
- name: Validate environment input
|
|
62
|
+
run: |
|
|
63
|
+
allowed="prod staging dev"
|
|
64
|
+
input="${{ inputs.environment }}"
|
|
65
|
+
if [[ ! " $allowed " =~ " ${input} " ]]; then
|
|
66
|
+
echo "::error::Invalid environment '${input}'. Allowed values: $allowed"
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
- name: Deploy to ${{ inputs.environment }}
|
|
71
|
+
run: echo "Deploying to ${{ inputs.environment }}"
|
|
72
|
+
- language: yaml
|
|
73
|
+
label: "Case-insensitive validation with normalisation"
|
|
74
|
+
code: |
|
|
75
|
+
- name: Validate and normalise environment input
|
|
76
|
+
id: validate
|
|
77
|
+
run: |
|
|
78
|
+
input=$(echo "${{ inputs.environment }}" | tr '[:upper:]' '[:lower:]')
|
|
79
|
+
allowed=("prod" "staging" "dev")
|
|
80
|
+
valid=false
|
|
81
|
+
for v in "${allowed[@]}"; do
|
|
82
|
+
[[ "$input" == "$v" ]] && valid=true && break
|
|
83
|
+
done
|
|
84
|
+
if [[ "$valid" != "true" ]]; then
|
|
85
|
+
echo "::error::Unrecognised environment '${{ inputs.environment }}' (normalised: '$input'). Allowed: ${allowed[*]}"
|
|
86
|
+
exit 1
|
|
87
|
+
fi
|
|
88
|
+
echo "environment=$input" >> "$GITHUB_OUTPUT"
|
|
89
|
+
prevention:
|
|
90
|
+
- "Treat type: choice as a UI hint only — always validate inputs programmatically inside the workflow."
|
|
91
|
+
- "Add an allowlist guard as the first step of any job that branches on a choice input."
|
|
92
|
+
- "Document valid values and casing rules in the input description field so API callers know the expected format."
|
|
93
|
+
- "Use actionlint to detect misspelled choices at authoring time before they reach production."
|
|
94
|
+
docs:
|
|
95
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_dispatch"
|
|
96
|
+
label: "GitHub Actions: workflow_dispatch event — inputs"
|
|
97
|
+
- url: "https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event"
|
|
98
|
+
label: "GitHub REST API: Create a workflow dispatch event"
|