@htekdev/actions-debugger 1.0.82 → 1.0.84
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/concurrency-timing/event-number-empty-on-push-collapses-concurrency.yml +90 -0
- package/errors/known-unsolved/step-log-output-truncated-64kb.yml +88 -0
- package/errors/known-unsolved/workflow-dispatch-api-no-run-id-in-response.yml +101 -0
- package/errors/runner-environment/git-commit-author-identity-unknown.yml +83 -0
- package/errors/silent-failures/skipped-step-job-output-silently-empty.yml +119 -0
- package/errors/triggers/pull-request-closed-fires-for-unmerged-prs.yml +79 -0
- package/errors/triggers/schedule-paths-filter-silently-ignored.yml +111 -0
- package/errors/yaml-syntax/reusable-workflow-with-secrets-context-rejected.yml +98 -0
- package/package.json +1 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
id: concurrency-timing-042
|
|
2
|
+
title: "github.event.number empty on push events collapses concurrency group across all branches"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- concurrency
|
|
7
|
+
- github-event-number
|
|
8
|
+
- push-event
|
|
9
|
+
- cancel-in-progress
|
|
10
|
+
- cross-branch
|
|
11
|
+
- pull-request
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'github\.event\.number\b'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "Run was cancelled due to concurrency group collision between push events on different branches"
|
|
17
|
+
- "Unexpected cancellation of push workflow by unrelated branch push"
|
|
18
|
+
root_cause: |
|
|
19
|
+
`github.event.number` contains the pull request or issue number for events that have one
|
|
20
|
+
(e.g., `pull_request`, `pull_request_review`, `issue_comment`). On `push` events,
|
|
21
|
+
`github.event.number` is undefined and evaluates to an empty string in expressions.
|
|
22
|
+
|
|
23
|
+
When a workflow is triggered by both `push` and `pull_request` events, and the
|
|
24
|
+
concurrency group key uses `github.event.number`, all push-triggered runs share a
|
|
25
|
+
single concurrency group key (the suffix becomes empty):
|
|
26
|
+
|
|
27
|
+
group: "${{ github.workflow }}-${{ github.event.number }}"
|
|
28
|
+
# PR run: "CI-123" (unique per PR number)
|
|
29
|
+
# Push run: "CI-" (ALL branch pushes share this single group!)
|
|
30
|
+
|
|
31
|
+
With `cancel-in-progress: true`, pushing to any branch cancels all other in-progress
|
|
32
|
+
push runs — even if they are on completely different branches. Developers intend to
|
|
33
|
+
serialize CI per PR but instead unintentionally cancel unrelated branch pushes.
|
|
34
|
+
fix: |
|
|
35
|
+
Use the `||` operator to fall back to `github.ref` when `github.event.number` is empty.
|
|
36
|
+
`github.ref` is always populated (e.g., `refs/heads/main`, `refs/pull/123/merge`) and
|
|
37
|
+
produces a unique key per branch or PR.
|
|
38
|
+
|
|
39
|
+
Alternatively, use `github.event.number || github.sha` if you want push runs to each
|
|
40
|
+
be independent (no cancellation between pushes) while still serializing per PR.
|
|
41
|
+
fix_code:
|
|
42
|
+
- language: yaml
|
|
43
|
+
label: "Wrong — all push runs share the same concurrency group key"
|
|
44
|
+
code: |
|
|
45
|
+
on:
|
|
46
|
+
push:
|
|
47
|
+
branches: [main, 'feature/**']
|
|
48
|
+
pull_request:
|
|
49
|
+
|
|
50
|
+
concurrency:
|
|
51
|
+
group: ${{ github.workflow }}-${{ github.event.number }}
|
|
52
|
+
# On push: group = "CI-" (empty number → all pushes share one group)
|
|
53
|
+
# On PR: group = "CI-123" (unique per PR)
|
|
54
|
+
cancel-in-progress: true
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: "Correct — fall back to github.ref so push runs are keyed per branch"
|
|
57
|
+
code: |
|
|
58
|
+
on:
|
|
59
|
+
push:
|
|
60
|
+
branches: [main, 'feature/**']
|
|
61
|
+
pull_request:
|
|
62
|
+
|
|
63
|
+
concurrency:
|
|
64
|
+
group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
|
|
65
|
+
# On push: group = "CI-refs/heads/main" (unique per branch)
|
|
66
|
+
# On PR: group = "CI-123" (unique per PR number)
|
|
67
|
+
cancel-in-progress: true
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: "Alternative — use github.ref for both triggers (no per-PR serialization)"
|
|
70
|
+
code: |
|
|
71
|
+
on:
|
|
72
|
+
push:
|
|
73
|
+
branches: [main, 'feature/**']
|
|
74
|
+
pull_request:
|
|
75
|
+
|
|
76
|
+
concurrency:
|
|
77
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
78
|
+
# On push: group = "CI-refs/heads/main"
|
|
79
|
+
# On PR: group = "CI-refs/pull/123/merge"
|
|
80
|
+
cancel-in-progress: true
|
|
81
|
+
prevention:
|
|
82
|
+
- "Always test concurrency group keys against all event types in the on: block"
|
|
83
|
+
- "Use github.event.number || github.ref as a safe pattern for mixed push/PR workflows"
|
|
84
|
+
- "Check that github.event.number is not undefined by printing it in a debug step when first setting up concurrency"
|
|
85
|
+
- "Prefer github.ref or github.ref_name as the base concurrency discriminator for branch-level serialization"
|
|
86
|
+
docs:
|
|
87
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idconcurrency"
|
|
88
|
+
label: "Workflow syntax: concurrency"
|
|
89
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#github-context"
|
|
90
|
+
label: "GitHub context — github.event.number availability by event type"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
id: known-unsolved-049
|
|
2
|
+
title: 'GitHub Actions step log output silently truncated after 64 KB — tail of large logs invisible'
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- logging
|
|
7
|
+
- truncation
|
|
8
|
+
- debug
|
|
9
|
+
- large-output
|
|
10
|
+
- verbose-build
|
|
11
|
+
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Log upload failed|Error uploading logs'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'log size exceeds'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
|
|
18
|
+
error_messages:
|
|
19
|
+
- "##[warning]The log file exceeds the limit."
|
|
20
|
+
- "##[warning]Some log data was not captured."
|
|
21
|
+
|
|
22
|
+
root_cause: |
|
|
23
|
+
GitHub Actions truncates the visible log output for each step at approximately
|
|
24
|
+
64 KB (65,536 bytes). Steps that produce large stdout or stderr — verbose builds,
|
|
25
|
+
test suites with thousands of tests, dependency installation logs, or debug-mode
|
|
26
|
+
tools — have their logs silently cut off at the truncation limit.
|
|
27
|
+
|
|
28
|
+
When logs are truncated, the last portion of the output (which often contains
|
|
29
|
+
the most important information — the actual error or failure message) becomes
|
|
30
|
+
invisible in the GitHub Actions UI. There is no prominent warning that truncation
|
|
31
|
+
occurred; only a small warning annotation may appear.
|
|
32
|
+
|
|
33
|
+
Common triggers:
|
|
34
|
+
- `npm install` with many packages in verbose mode
|
|
35
|
+
- Maven/Gradle builds with `--info` or `--debug` flags
|
|
36
|
+
- Test runners printing results for thousands of test cases
|
|
37
|
+
- CMake or Make with verbose output enabled
|
|
38
|
+
- Custom scripts that echo large data structures for debugging
|
|
39
|
+
|
|
40
|
+
fix: |
|
|
41
|
+
GitHub has no setting to increase the per-step log limit. The workarounds
|
|
42
|
+
involve reducing log volume or capturing overflow output to an artifact file.
|
|
43
|
+
|
|
44
|
+
Option 1 — Reduce log verbosity: Remove `--verbose`, `--debug`, or `--info`
|
|
45
|
+
flags from build/install commands that produce excessive output.
|
|
46
|
+
|
|
47
|
+
Option 2 — Redirect overflow to artifact: Pipe output to a file and upload it
|
|
48
|
+
as an artifact. The complete log is preserved and downloadable.
|
|
49
|
+
|
|
50
|
+
Option 3 — Split the step: Break one large step into multiple smaller steps
|
|
51
|
+
so each step's output stays under the limit.
|
|
52
|
+
|
|
53
|
+
fix_code:
|
|
54
|
+
- language: yaml
|
|
55
|
+
label: 'Redirect large output to artifact file'
|
|
56
|
+
code: |
|
|
57
|
+
- name: Run verbose build (output to file)
|
|
58
|
+
run: |
|
|
59
|
+
# Tee output: display live AND capture to file for artifact upload
|
|
60
|
+
set -o pipefail
|
|
61
|
+
make build 2>&1 | tee build-output.log
|
|
62
|
+
|
|
63
|
+
- name: Upload full build log as artifact
|
|
64
|
+
if: always()
|
|
65
|
+
uses: actions/upload-artifact@v4
|
|
66
|
+
with:
|
|
67
|
+
name: build-log
|
|
68
|
+
path: build-output.log
|
|
69
|
+
retention-days: 7
|
|
70
|
+
- language: yaml
|
|
71
|
+
label: 'Reduce log verbosity at build tool level'
|
|
72
|
+
code: |
|
|
73
|
+
- name: Install dependencies (quiet)
|
|
74
|
+
run: npm install --silent # suppress per-package progress output
|
|
75
|
+
|
|
76
|
+
- name: Build (no verbose)
|
|
77
|
+
run: make build # omit -v or VERBOSE=1
|
|
78
|
+
|
|
79
|
+
prevention:
|
|
80
|
+
- 'Avoid passing `--verbose`, `--debug`, or `--info` flags to build tools in CI unless actively debugging a specific failure.'
|
|
81
|
+
- 'Use `2>&1 | tee output.log` with artifact upload for any step expected to produce large output.'
|
|
82
|
+
- 'Split long-running steps into smaller focused steps so each stays well under the 64 KB log limit.'
|
|
83
|
+
|
|
84
|
+
docs:
|
|
85
|
+
- url: 'https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/monitoring-workflows/using-the-github-actions-debugger'
|
|
86
|
+
label: 'Using the GitHub Actions debugger — GitHub Docs'
|
|
87
|
+
- url: 'https://github.com/actions/upload-artifact'
|
|
88
|
+
label: 'actions/upload-artifact — GitHub Actions'
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
id: known-unsolved-050
|
|
2
|
+
title: "workflow_dispatch REST API dispatch returns 204 No Content — triggered run_id unavailable"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- workflow_dispatch
|
|
7
|
+
- rest-api
|
|
8
|
+
- run-id
|
|
9
|
+
- polling
|
|
10
|
+
- automation
|
|
11
|
+
- cd-pipeline
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'POST.*dispatches.*204|dispatches.*No Content'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "204 No Content"
|
|
17
|
+
- "Unable to correlate triggered workflow run"
|
|
18
|
+
- "no run_id in dispatch response"
|
|
19
|
+
root_cause: |
|
|
20
|
+
The GitHub Actions REST API endpoint
|
|
21
|
+
`POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches`
|
|
22
|
+
always returns HTTP 204 No Content on success — there is no run_id or any identifier
|
|
23
|
+
in the response body. GitHub creates the workflow run asynchronously after the API call
|
|
24
|
+
returns, so the run may not exist in the system for several seconds. There is no built-in
|
|
25
|
+
way to correlate a `workflow_dispatch` API call to the resulting run_id.
|
|
26
|
+
|
|
27
|
+
This is a persistent limitation reported since 2020 on the GitHub feedback forum with
|
|
28
|
+
thousands of upvotes. Developers building CD pipelines that need to trigger a workflow
|
|
29
|
+
and then monitor or gate on its result are forced to use fragile polling heuristics.
|
|
30
|
+
fix: |
|
|
31
|
+
No direct fix — this is a GitHub platform limitation. Use one of these workarounds:
|
|
32
|
+
|
|
33
|
+
1. **Inject a correlation ID via inputs**: Pass a unique value (e.g., UUID) as a
|
|
34
|
+
`workflow_dispatch` input. After dispatch, poll `GET /repos/.../actions/runs?event=workflow_dispatch&branch=<branch>`
|
|
35
|
+
and match runs whose `inputs` contain the correlation ID.
|
|
36
|
+
|
|
37
|
+
2. **Use workflow_run event callback**: Design the triggered workflow to complete and
|
|
38
|
+
then fire a `workflow_run` event that your orchestrating workflow listens for.
|
|
39
|
+
|
|
40
|
+
3. **Switch to repository_dispatch**: `repository_dispatch` with a unique `client_payload`
|
|
41
|
+
field lets you include a correlation ID. The triggered workflow can write it to an artifact
|
|
42
|
+
or output for later retrieval.
|
|
43
|
+
|
|
44
|
+
4. **Third-party action**: Use `peter-evans/workflow-dispatch@v3` which implements polling
|
|
45
|
+
heuristics to approximate the run_id after dispatch.
|
|
46
|
+
fix_code:
|
|
47
|
+
- language: yaml
|
|
48
|
+
label: "Inject correlation ID via workflow_dispatch input — then poll to find run"
|
|
49
|
+
code: |
|
|
50
|
+
# Triggering workflow or script passes a unique correlation ID:
|
|
51
|
+
# POST /repos/{owner}/{repo}/actions/workflows/deploy.yml/dispatches
|
|
52
|
+
# { "ref": "main", "inputs": { "correlation_id": "abc-123-uuid" } }
|
|
53
|
+
|
|
54
|
+
# Triggered workflow (deploy.yml) writes ID to output artifact:
|
|
55
|
+
on:
|
|
56
|
+
workflow_dispatch:
|
|
57
|
+
inputs:
|
|
58
|
+
correlation_id:
|
|
59
|
+
description: 'Unique ID for run correlation'
|
|
60
|
+
required: false
|
|
61
|
+
default: ''
|
|
62
|
+
|
|
63
|
+
jobs:
|
|
64
|
+
deploy:
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
steps:
|
|
67
|
+
- run: echo "run_id=${{ github.run_id }}" >> correlation.txt
|
|
68
|
+
- uses: actions/upload-artifact@v4
|
|
69
|
+
with:
|
|
70
|
+
name: correlation-${{ inputs.correlation_id }}
|
|
71
|
+
path: correlation.txt
|
|
72
|
+
- language: yaml
|
|
73
|
+
label: "Use repository_dispatch with client_payload for reliable correlation"
|
|
74
|
+
code: |
|
|
75
|
+
# Trigger with unique client_payload via API:
|
|
76
|
+
# POST /repos/{owner}/{repo}/dispatches
|
|
77
|
+
# { "event_type": "deploy", "client_payload": { "job_id": "build-789" } }
|
|
78
|
+
|
|
79
|
+
on:
|
|
80
|
+
repository_dispatch:
|
|
81
|
+
types: [deploy]
|
|
82
|
+
|
|
83
|
+
jobs:
|
|
84
|
+
deploy:
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
steps:
|
|
87
|
+
- run: |
|
|
88
|
+
echo "Triggered by job: ${{ github.event.client_payload.job_id }}"
|
|
89
|
+
echo "This run ID: ${{ github.run_id }}"
|
|
90
|
+
prevention:
|
|
91
|
+
- "Design CD pipelines using workflow_run event callbacks instead of polling after dispatch"
|
|
92
|
+
- "Include a unique correlation_id input in all workflow_dispatch-triggered workflows"
|
|
93
|
+
- "Track the GitHub public roadmap — run_id in dispatch response is a long-requested feature"
|
|
94
|
+
- "Consider repository_dispatch for machine-to-machine triggers needing reliable correlation"
|
|
95
|
+
docs:
|
|
96
|
+
- url: "https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event"
|
|
97
|
+
label: "REST API: Create a workflow dispatch event (returns 204 No Content)"
|
|
98
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run"
|
|
99
|
+
label: "workflow_run event — callback pattern for monitoring triggered workflows"
|
|
100
|
+
- url: "https://github.com/peter-evans/workflow-dispatch"
|
|
101
|
+
label: "peter-evans/workflow-dispatch — community action with polling heuristics"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
id: runner-environment-149
|
|
2
|
+
title: '`git commit` fails with "Author identity unknown" — actions/checkout does not configure git user identity'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- checkout
|
|
7
|
+
- identity
|
|
8
|
+
- commit
|
|
9
|
+
- user-email
|
|
10
|
+
- author-identity
|
|
11
|
+
- automation
|
|
12
|
+
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'Author identity unknown'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'please tell me who you are'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
- regex: 'user\.email not configured'
|
|
19
|
+
flags: 'i'
|
|
20
|
+
|
|
21
|
+
error_messages:
|
|
22
|
+
- "Author identity unknown"
|
|
23
|
+
- "*** Please tell me who you are."
|
|
24
|
+
- "fatal: empty ident name (for <>) not allowed"
|
|
25
|
+
- "error: empty ident name (for <>) not allowed"
|
|
26
|
+
|
|
27
|
+
root_cause: |
|
|
28
|
+
`actions/checkout` sets up the repository and configures a `GITHUB_TOKEN`-based
|
|
29
|
+
credential helper for pushing, but it does NOT configure a git user identity
|
|
30
|
+
(`user.email` and `user.name`). When a workflow step subsequently creates a
|
|
31
|
+
commit, git requires committer identity to record in the commit object.
|
|
32
|
+
|
|
33
|
+
GitHub-hosted runners have no system-level git identity configured. Without
|
|
34
|
+
an explicit configuration step, git rejects the commit with the error
|
|
35
|
+
"Author identity unknown" and prompts for `user.email` and `user.name`.
|
|
36
|
+
|
|
37
|
+
This is one of the most common errors in workflows that automate commits:
|
|
38
|
+
auto-formatting fixes, changelog updates, version bumps, license header
|
|
39
|
+
injection, and documentation generation workflows that write changes back
|
|
40
|
+
to the repository.
|
|
41
|
+
|
|
42
|
+
fix: |
|
|
43
|
+
Add an identity configuration step immediately after `actions/checkout`.
|
|
44
|
+
GitHub provides an official `github-actions[bot]` identity with a documented
|
|
45
|
+
no-reply email address that is suitable for automated commits. This makes
|
|
46
|
+
automated commits clearly attributable in repository history.
|
|
47
|
+
|
|
48
|
+
The numeric user ID prefix in the email (`41898282+`) is the GitHub user ID
|
|
49
|
+
for the `github-actions[bot]` service account and ensures the commits are
|
|
50
|
+
linked to that identity in the GitHub UI.
|
|
51
|
+
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: 'Add identity configuration step after checkout'
|
|
55
|
+
code: |
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v4
|
|
58
|
+
with:
|
|
59
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
60
|
+
|
|
61
|
+
- name: Set bot identity for automated commits
|
|
62
|
+
run: |
|
|
63
|
+
echo "Configuring identity for automated commits"
|
|
64
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
65
|
+
git config user.name "github-actions[bot]"
|
|
66
|
+
|
|
67
|
+
- name: Apply and commit changes
|
|
68
|
+
run: |
|
|
69
|
+
git add .
|
|
70
|
+
git commit -m "chore: automated update [skip ci]"
|
|
71
|
+
git push
|
|
72
|
+
|
|
73
|
+
prevention:
|
|
74
|
+
- 'Add identity configuration as the first step after checkout in any job that creates commits.'
|
|
75
|
+
- 'Use the `github-actions[bot]` no-reply email to make automated commits attributable without a real user email.'
|
|
76
|
+
- 'Consider setting identity at workflow level using a reusable composite action so all jobs inherit it automatically.'
|
|
77
|
+
- 'Use `--global` flag if the workflow uses multiple checkouts or subdirectory checkouts that all need the same identity.'
|
|
78
|
+
|
|
79
|
+
docs:
|
|
80
|
+
- url: 'https://github.com/actions/checkout'
|
|
81
|
+
label: 'actions/checkout — GitHub Actions'
|
|
82
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-jobs-in-a-workflow'
|
|
83
|
+
label: 'Using jobs in a workflow — GitHub Docs'
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
id: silent-failures-078
|
|
2
|
+
title: "Skipped step leaves job-level outputs: silently empty — downstream jobs receive empty string"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- job-outputs
|
|
7
|
+
- skipped-step
|
|
8
|
+
- outputs
|
|
9
|
+
- if-condition
|
|
10
|
+
- conditional
|
|
11
|
+
- downstream-jobs
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'steps\.[a-z_][a-z0-9_-]*\.outputs\.[a-z_]'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "needs.<job>.outputs.<key> is empty string when producing step was skipped"
|
|
17
|
+
- "Job output silently empty — step that writes output was conditionally skipped"
|
|
18
|
+
root_cause: |
|
|
19
|
+
A job's `outputs:` block maps keys to values using `${{ steps.<id>.outputs.<key> }}`
|
|
20
|
+
expressions. These expressions are evaluated at the end of the job, after all steps have
|
|
21
|
+
run. If the step referenced in the output mapping is skipped (due to an `if:` condition
|
|
22
|
+
evaluating to false), the step never runs and never writes to `$GITHUB_OUTPUT`. The
|
|
23
|
+
output expression evaluates to an empty string with no warning.
|
|
24
|
+
|
|
25
|
+
Downstream jobs that depend on this output via `needs.<job>.outputs.<key>` receive an
|
|
26
|
+
empty string and continue running without error. This silent empty propagation causes
|
|
27
|
+
incorrect behavior: deploy jobs receiving an empty version string, notification jobs
|
|
28
|
+
receiving an empty status, or matrix jobs receiving an empty configuration list.
|
|
29
|
+
|
|
30
|
+
This is distinct from the `skipped-job-outputs-empty-string` limitation (where the
|
|
31
|
+
entire job is skipped) — here the job itself runs successfully, but one or more of its
|
|
32
|
+
steps are conditionally skipped.
|
|
33
|
+
fix: |
|
|
34
|
+
Ensure the step that writes the output always runs, or provide a fallback value.
|
|
35
|
+
Use `if: always()` on the step if it should run regardless of prior step results.
|
|
36
|
+
Use a separate step to set a default/fallback output for the skipped case.
|
|
37
|
+
Alternatively, restructure the workflow so that the output-producing step is never
|
|
38
|
+
guarded by a condition that might prevent it from running.
|
|
39
|
+
fix_code:
|
|
40
|
+
- language: yaml
|
|
41
|
+
label: "Wrong — output step conditionally skipped, downstream sees empty string"
|
|
42
|
+
code: |
|
|
43
|
+
jobs:
|
|
44
|
+
build:
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
outputs:
|
|
47
|
+
version: ${{ steps.get-version.outputs.version }}
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@v4
|
|
50
|
+
|
|
51
|
+
- name: Get version (only on tags)
|
|
52
|
+
id: get-version
|
|
53
|
+
if: startsWith(github.ref, 'refs/tags/') # skipped on branch pushes!
|
|
54
|
+
run: echo "version=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
|
|
55
|
+
|
|
56
|
+
deploy:
|
|
57
|
+
needs: build
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
steps:
|
|
60
|
+
- run: |
|
|
61
|
+
echo "Deploying version: ${{ needs.build.outputs.version }}"
|
|
62
|
+
# On branch push: prints "Deploying version: " (empty — step was skipped)
|
|
63
|
+
- language: yaml
|
|
64
|
+
label: "Correct — provide a fallback step to always set the output"
|
|
65
|
+
code: |
|
|
66
|
+
jobs:
|
|
67
|
+
build:
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
outputs:
|
|
70
|
+
version: ${{ steps.get-version.outputs.version }}
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@v4
|
|
73
|
+
|
|
74
|
+
- name: Get version from tag
|
|
75
|
+
id: get-version
|
|
76
|
+
run: |
|
|
77
|
+
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
|
78
|
+
echo "version=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
|
|
79
|
+
else
|
|
80
|
+
echo "version=dev-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
|
|
81
|
+
fi
|
|
82
|
+
# Always runs — handles both tag and branch pushes
|
|
83
|
+
|
|
84
|
+
deploy:
|
|
85
|
+
needs: build
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
steps:
|
|
88
|
+
- run: echo "Deploying version ${{ needs.build.outputs.version }}"
|
|
89
|
+
- language: yaml
|
|
90
|
+
label: "Alternative — gate deploy job to only run when version is set"
|
|
91
|
+
code: |
|
|
92
|
+
jobs:
|
|
93
|
+
build:
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
outputs:
|
|
96
|
+
version: ${{ steps.get-version.outputs.version }}
|
|
97
|
+
steps:
|
|
98
|
+
- uses: actions/checkout@v4
|
|
99
|
+
- name: Get version (only on tags)
|
|
100
|
+
id: get-version
|
|
101
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
102
|
+
run: echo "version=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
|
|
103
|
+
|
|
104
|
+
deploy:
|
|
105
|
+
needs: build
|
|
106
|
+
if: needs.build.outputs.version != '' # only run when version was set
|
|
107
|
+
runs-on: ubuntu-latest
|
|
108
|
+
steps:
|
|
109
|
+
- run: echo "Deploying ${{ needs.build.outputs.version }}"
|
|
110
|
+
prevention:
|
|
111
|
+
- "Audit all job outputs: blocks — for each step reference, verify it always runs"
|
|
112
|
+
- "Avoid conditional if: guards on steps that produce job-level outputs unless a fallback step provides the same output key"
|
|
113
|
+
- "Add an explicit check step at the end of each job that validates required outputs are non-empty"
|
|
114
|
+
- "Use actionlint — it warns when output-producing steps have if: conditions that may skip them"
|
|
115
|
+
docs:
|
|
116
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs"
|
|
117
|
+
label: "Passing information between jobs — job outputs"
|
|
118
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows"
|
|
119
|
+
label: "Events that trigger workflows — job outputs with conditional steps"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
id: triggers-057
|
|
2
|
+
title: '`on.pull_request types: [closed]` fires for both merged AND unmerged PR closures — use `if: merged == true` guard'
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- pull_request
|
|
7
|
+
- closed
|
|
8
|
+
- merged
|
|
9
|
+
- event-types
|
|
10
|
+
- deploy-on-merge
|
|
11
|
+
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'types:\s*\[.*closed.*\]'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'on:\s*\n\s+pull_request:\s*\n\s+types:\s*\[.*closed'
|
|
16
|
+
flags: 'im'
|
|
17
|
+
|
|
18
|
+
error_messages:
|
|
19
|
+
- "github.event.action: closed"
|
|
20
|
+
- "github.event.pull_request.merged: false"
|
|
21
|
+
|
|
22
|
+
root_cause: |
|
|
23
|
+
The `pull_request` event `closed` type fires whenever a PR is closed — regardless
|
|
24
|
+
of whether it was merged or simply closed without merging. GitHub does not provide
|
|
25
|
+
a `merged` type for the `pull_request` event.
|
|
26
|
+
|
|
27
|
+
Developers commonly use `types: [closed]` to trigger deployment or post-merge
|
|
28
|
+
workflows, assuming `closed` is equivalent to "merged into the base branch". In
|
|
29
|
+
reality, a PR can be closed (abandoned, rejected) without being merged, and the
|
|
30
|
+
`closed` event fires in both cases.
|
|
31
|
+
|
|
32
|
+
The distinction is only available via `github.event.pull_request.merged` (a
|
|
33
|
+
boolean) or `github.event.pull_request.merge_commit_sha` (non-null if merged).
|
|
34
|
+
Without an explicit check, workflows using `types: [closed]` will run on
|
|
35
|
+
abandoned PRs, causing spurious deployments, incorrect release triggers, or
|
|
36
|
+
unnecessary job runs.
|
|
37
|
+
|
|
38
|
+
fix: |
|
|
39
|
+
Add an `if:` condition to the job or workflow that checks
|
|
40
|
+
`github.event.pull_request.merged == true` to ensure the workflow only
|
|
41
|
+
proceeds when the PR was actually merged.
|
|
42
|
+
|
|
43
|
+
fix_code:
|
|
44
|
+
- language: yaml
|
|
45
|
+
label: 'Guard job with merged == true check'
|
|
46
|
+
code: |
|
|
47
|
+
on:
|
|
48
|
+
pull_request:
|
|
49
|
+
types: [closed]
|
|
50
|
+
|
|
51
|
+
jobs:
|
|
52
|
+
deploy:
|
|
53
|
+
# Only run when PR was merged, not when simply closed
|
|
54
|
+
if: github.event.pull_request.merged == true
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v4
|
|
58
|
+
- run: ./deploy.sh
|
|
59
|
+
- language: yaml
|
|
60
|
+
label: 'Equivalent guard using merge_commit_sha'
|
|
61
|
+
code: |
|
|
62
|
+
jobs:
|
|
63
|
+
notify:
|
|
64
|
+
if: github.event.pull_request.merge_commit_sha != ''
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
steps:
|
|
67
|
+
- name: Notify on merge
|
|
68
|
+
run: echo "PR merged at ${{ github.event.pull_request.merge_commit_sha }}"
|
|
69
|
+
|
|
70
|
+
prevention:
|
|
71
|
+
- 'Never use `types: [closed]` without an `if: github.event.pull_request.merged == true` guard for merge-triggered workflows.'
|
|
72
|
+
- 'Remember: there is no `merged` event type for `pull_request` — `closed` is the closest type, but it requires the merged guard.'
|
|
73
|
+
- 'Test your workflow by manually closing a PR without merging and verifying the workflow does not run (or is skipped).'
|
|
74
|
+
|
|
75
|
+
docs:
|
|
76
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request'
|
|
77
|
+
label: 'pull_request event — GitHub Docs'
|
|
78
|
+
- url: 'https://docs.github.com/en/webhooks/webhook-events-and-payloads#pull_request'
|
|
79
|
+
label: 'pull_request webhook payload — GitHub Docs'
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
id: triggers-058
|
|
2
|
+
title: "on.schedule ignores paths: filter — scheduled workflows always run"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- schedule
|
|
7
|
+
- paths
|
|
8
|
+
- cron
|
|
9
|
+
- trigger
|
|
10
|
+
- filter
|
|
11
|
+
- paths-ignore
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'schedule.*paths:|paths:.*cron:'
|
|
14
|
+
flags: 'si'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "Scheduled workflow runs even when no matching files changed"
|
|
17
|
+
- "paths: filter has no effect on scheduled events"
|
|
18
|
+
root_cause: |
|
|
19
|
+
The `paths:` and `paths-ignore:` filters are only evaluated for `push` and `pull_request`
|
|
20
|
+
events, where GitHub can compare the changed files in the triggering commit against the
|
|
21
|
+
filter patterns. `on.schedule` events are time-based and have no associated commit or
|
|
22
|
+
changeset — there are no paths to compare. GitHub silently ignores any `paths:` or
|
|
23
|
+
`paths-ignore:` configuration placed under a `schedule:` trigger.
|
|
24
|
+
|
|
25
|
+
This commonly happens when a developer copies a `push:` trigger block that includes
|
|
26
|
+
`paths:` filtering and applies it to a `schedule:` block expecting the same filtering
|
|
27
|
+
behavior. The workflow runs on every scheduled interval regardless of what files have
|
|
28
|
+
changed. The `paths:` keys are not flagged as invalid — they are simply ignored without
|
|
29
|
+
any warning in the workflow or run logs.
|
|
30
|
+
fix: |
|
|
31
|
+
Remove `paths:` and `paths-ignore:` from `on.schedule:` blocks — they have no effect.
|
|
32
|
+
|
|
33
|
+
If you need the scheduled workflow to skip execution when certain conditions are not
|
|
34
|
+
met, implement gate logic inside the workflow:
|
|
35
|
+
|
|
36
|
+
- Use an early job that checks file modification times or an external flag and sets
|
|
37
|
+
an output. Downstream jobs use `if: needs.check.outputs.should_run == 'true'`.
|
|
38
|
+
- Use the `dorny/paths-filter` action to detect file changes since a known reference
|
|
39
|
+
(e.g., the last successful run's commit SHA stored in a cache or artifact).
|
|
40
|
+
- If the schedule purpose is unrelated to file changes (e.g., dependency audits,
|
|
41
|
+
health checks), no conditional gate is needed — let it run on schedule.
|
|
42
|
+
fix_code:
|
|
43
|
+
- language: yaml
|
|
44
|
+
label: "Wrong — paths: under schedule: is silently ignored"
|
|
45
|
+
code: |
|
|
46
|
+
on:
|
|
47
|
+
schedule:
|
|
48
|
+
- cron: '0 2 * * *'
|
|
49
|
+
paths: # silently ignored — schedule always runs
|
|
50
|
+
- 'src/**'
|
|
51
|
+
- 'package.json'
|
|
52
|
+
push:
|
|
53
|
+
branches: [main]
|
|
54
|
+
paths: # this paths: applies only to the push: trigger
|
|
55
|
+
- 'src/**'
|
|
56
|
+
- 'package.json'
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: "Correct — paths: filter on push only; schedule unconditional or gated in-workflow"
|
|
59
|
+
code: |
|
|
60
|
+
on:
|
|
61
|
+
schedule:
|
|
62
|
+
- cron: '0 2 * * *' # no paths: here — schedule always runs
|
|
63
|
+
push:
|
|
64
|
+
branches: [main]
|
|
65
|
+
paths: # paths: filter applies to push trigger only
|
|
66
|
+
- 'src/**'
|
|
67
|
+
- 'package.json'
|
|
68
|
+
|
|
69
|
+
jobs:
|
|
70
|
+
build:
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@v4
|
|
74
|
+
- run: npm run build
|
|
75
|
+
|
|
76
|
+
# To conditionally skip a scheduled run, use an in-workflow gate:
|
|
77
|
+
# jobs:
|
|
78
|
+
# check-changes:
|
|
79
|
+
# runs-on: ubuntu-latest
|
|
80
|
+
# outputs:
|
|
81
|
+
# changed: ${{ steps.filter.outputs.src }}
|
|
82
|
+
# steps:
|
|
83
|
+
# - uses: actions/checkout@v4
|
|
84
|
+
# with:
|
|
85
|
+
# fetch-depth: 2
|
|
86
|
+
# - uses: dorny/paths-filter@v3
|
|
87
|
+
# id: filter
|
|
88
|
+
# with:
|
|
89
|
+
# filters: |
|
|
90
|
+
# src:
|
|
91
|
+
# - 'src/**'
|
|
92
|
+
#
|
|
93
|
+
# build:
|
|
94
|
+
# needs: check-changes
|
|
95
|
+
# if: needs.check-changes.outputs.changed == 'true' || github.event_name == 'push'
|
|
96
|
+
# runs-on: ubuntu-latest
|
|
97
|
+
# steps:
|
|
98
|
+
# - uses: actions/checkout@v4
|
|
99
|
+
# - run: npm run build
|
|
100
|
+
prevention:
|
|
101
|
+
- "Only use paths: and paths-ignore: under push: and pull_request: trigger blocks"
|
|
102
|
+
- "Verify which filters apply to which events using the GitHub Actions syntax reference"
|
|
103
|
+
- "actionlint reports a warning when paths: is used under schedule: — run it in CI"
|
|
104
|
+
- "For scheduled conditional logic, gate using job if: conditions on in-workflow checks"
|
|
105
|
+
docs:
|
|
106
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onpushpull_requestpull_request_targetpaths"
|
|
107
|
+
label: "Workflow syntax: paths filter (applies to push and pull_request only)"
|
|
108
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule"
|
|
109
|
+
label: "Events that trigger workflows: schedule"
|
|
110
|
+
- url: "https://github.com/dorny/paths-filter"
|
|
111
|
+
label: "dorny/paths-filter — detect file changes inside a scheduled workflow"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
id: yaml-syntax-052
|
|
2
|
+
title: '`secrets` context not available in `with:` inputs for reusable workflow calls — use `secrets:` block instead'
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- reusable-workflows
|
|
7
|
+
- secrets
|
|
8
|
+
- with
|
|
9
|
+
- workflow-call
|
|
10
|
+
- secrets-context
|
|
11
|
+
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Context access might be invalid: secrets'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'secrets\.[A-Z_][A-Z0-9_]*'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
|
|
18
|
+
error_messages:
|
|
19
|
+
- "Context access might be invalid: secrets"
|
|
20
|
+
- "The workflow is not valid. .github/workflows/caller.yml: Unrecognized named-value: 'secrets'"
|
|
21
|
+
- "Unrecognized named-value: 'secrets'. Located at position 1 within expression: secrets.MY_TOKEN"
|
|
22
|
+
|
|
23
|
+
root_cause: |
|
|
24
|
+
When calling a reusable workflow, the `with:` key (which passes inputs) does NOT
|
|
25
|
+
have access to the `secrets` context. The `secrets` context is intentionally
|
|
26
|
+
restricted to the `secrets:` block of the reusable workflow call to prevent
|
|
27
|
+
secrets from being accidentally exposed as plain-text input values.
|
|
28
|
+
|
|
29
|
+
This restriction applies ONLY to reusable workflow calls (`jobs.<id>.uses:`).
|
|
30
|
+
Regular action steps (`steps.<id>.uses:`) DO allow `${{ secrets.MY_SECRET }}`
|
|
31
|
+
in their `with:` blocks.
|
|
32
|
+
|
|
33
|
+
Attempting to pass a secret via `with:` in a reusable workflow call results in
|
|
34
|
+
an actionlint error or a runtime error: "Context access might be invalid: secrets".
|
|
35
|
+
Even if it were allowed, passing secrets as `with:` inputs would expose the
|
|
36
|
+
secret value in the workflow logs as a plain-text input.
|
|
37
|
+
|
|
38
|
+
The correct mechanism is the `secrets:` mapping on the calling job, which
|
|
39
|
+
explicitly maps caller secrets to the callee's declared `on.workflow_call.secrets`
|
|
40
|
+
parameters. This preserves masking and audit trail.
|
|
41
|
+
|
|
42
|
+
fix: |
|
|
43
|
+
Move secret values from `with:` to the `secrets:` block of the reusable
|
|
44
|
+
workflow call. Ensure the callee workflow declares the secret under
|
|
45
|
+
`on.workflow_call.secrets` with a matching name.
|
|
46
|
+
|
|
47
|
+
fix_code:
|
|
48
|
+
- language: yaml
|
|
49
|
+
label: 'Caller workflow — pass secrets via secrets: not with:'
|
|
50
|
+
code: |
|
|
51
|
+
# BAD: secrets context not available in with:
|
|
52
|
+
# jobs:
|
|
53
|
+
# call:
|
|
54
|
+
# uses: ./.github/workflows/deploy.yml
|
|
55
|
+
# with:
|
|
56
|
+
# token: ${{ secrets.DEPLOY_TOKEN }} # Error: secrets context invalid here
|
|
57
|
+
|
|
58
|
+
# GOOD: pass secrets via secrets: block
|
|
59
|
+
jobs:
|
|
60
|
+
call:
|
|
61
|
+
uses: ./.github/workflows/deploy.yml
|
|
62
|
+
with:
|
|
63
|
+
environment: production # non-secret inputs go here
|
|
64
|
+
secrets:
|
|
65
|
+
deploy-token: ${{ secrets.DEPLOY_TOKEN }} # secrets go here
|
|
66
|
+
- language: yaml
|
|
67
|
+
label: 'Callee workflow — declare secrets under on.workflow_call.secrets'
|
|
68
|
+
code: |
|
|
69
|
+
# .github/workflows/deploy.yml
|
|
70
|
+
on:
|
|
71
|
+
workflow_call:
|
|
72
|
+
inputs:
|
|
73
|
+
environment:
|
|
74
|
+
type: string
|
|
75
|
+
required: true
|
|
76
|
+
secrets:
|
|
77
|
+
deploy-token:
|
|
78
|
+
required: true
|
|
79
|
+
|
|
80
|
+
jobs:
|
|
81
|
+
deploy:
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
steps:
|
|
84
|
+
- name: Use the secret
|
|
85
|
+
env:
|
|
86
|
+
TOKEN: ${{ secrets.deploy-token }}
|
|
87
|
+
run: echo "Deploying to ${{ inputs.environment }}"
|
|
88
|
+
|
|
89
|
+
prevention:
|
|
90
|
+
- 'Never use `${{ secrets.* }}` inside `with:` of a reusable workflow call — the `secrets` context is blocked there.'
|
|
91
|
+
- 'Declare all required secrets in the callee under `on.workflow_call.secrets:` and pass them via `secrets:` in the caller.'
|
|
92
|
+
- 'Run actionlint in CI to catch `secrets` context misuse before it reaches runtime.'
|
|
93
|
+
|
|
94
|
+
docs:
|
|
95
|
+
- url: 'https://docs.github.com/en/actions/sharing-automations/reusing-workflows#using-inputs-and-secrets-in-a-reusable-workflow'
|
|
96
|
+
label: 'Using inputs and secrets in a reusable workflow — GitHub Docs'
|
|
97
|
+
- url: 'https://docs.github.com/en/actions/sharing-automations/reusing-workflows#passing-secrets-to-called-workflows'
|
|
98
|
+
label: 'Passing secrets to called workflows — GitHub Docs'
|
package/package.json
CHANGED