@htekdev/actions-debugger 1.0.27 → 1.0.29
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/hashfiles-empty-result-cache-key-collision.yml +78 -0
- package/errors/caching-artifacts/restore-keys-cross-arch-cache-mismatch.yml +80 -0
- package/errors/concurrency-timing/cleanup-job-if-cancelled-skipped-by-concurrency.yml +86 -0
- package/errors/concurrency-timing/timeout-minutes-queue-wait-not-included.yml +72 -0
- package/errors/runner-environment/container-job-entrypoint-overridden.yml +90 -0
- package/errors/runner-environment/github-path-prepend-breaks-container-no-path-env.yml +80 -0
- package/errors/silent-failures/event-inputs-empty-workflow-call.yml +81 -0
- package/errors/triggers/workflow-dispatch-button-missing-not-default-branch.yml +94 -0
- package/errors/triggers/workflow-run-skipped-when-trigger-workflow-filtered.yml +88 -0
- package/package.json +1 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
id: 'caching-artifacts-028'
|
|
2
|
+
title: 'hashFiles() returns empty string when no files match pattern, causing cache key collision across all runs'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- hashfiles
|
|
7
|
+
- cache-key
|
|
8
|
+
- empty-string
|
|
9
|
+
- collision
|
|
10
|
+
- lock-file
|
|
11
|
+
- monorepo
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'hashFiles\('
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- 'Cache hit for key:'
|
|
17
|
+
- 'hashFiles result is empty string'
|
|
18
|
+
root_cause: |
|
|
19
|
+
When hashFiles('**/package-lock.json') finds no matching files, it returns an empty
|
|
20
|
+
string instead of failing. A cache key like:
|
|
21
|
+
${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
22
|
+
evaluates to:
|
|
23
|
+
Linux-node-
|
|
24
|
+
(no hash suffix). All workflow runs — regardless of their actual dependency state —
|
|
25
|
+
then share this single cache key. The first run to complete saves its node_modules
|
|
26
|
+
under this key; every subsequent run gets a stale cache hit with potentially outdated
|
|
27
|
+
or wrong dependencies.
|
|
28
|
+
|
|
29
|
+
The cache-hit output shows 'true' and the step succeeds with no warning. Developers
|
|
30
|
+
see unexpectedly fast runs (cache always hits) but may encounter subtle dependency
|
|
31
|
+
staleness bugs.
|
|
32
|
+
|
|
33
|
+
Behavior varies across versions: actions/toolkit prior to 1.9.0 threw an exception
|
|
34
|
+
on empty results; later versions silently return empty string, making the collapsing
|
|
35
|
+
key the default behavior for repositories without the expected lock file.
|
|
36
|
+
fix: |
|
|
37
|
+
Guard hashFiles() with a fallback value so the cache key is never incomplete when
|
|
38
|
+
no matching files exist. Using || github.sha or || github.run_id ensures each run
|
|
39
|
+
gets a unique key when no lock file is present, preventing stale cache collisions.
|
|
40
|
+
fix_code:
|
|
41
|
+
- language: yaml
|
|
42
|
+
label: 'Add fallback to hashFiles to prevent empty cache key'
|
|
43
|
+
code: |
|
|
44
|
+
- uses: actions/cache@v4
|
|
45
|
+
with:
|
|
46
|
+
path: ~/.npm
|
|
47
|
+
# Fallback to github.sha when no lock file exists — prevents key collision
|
|
48
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') || github.sha }}
|
|
49
|
+
restore-keys: |
|
|
50
|
+
${{ runner.os }}-node-
|
|
51
|
+
- language: yaml
|
|
52
|
+
label: 'Verify cache is populated before relying on it'
|
|
53
|
+
code: |
|
|
54
|
+
- uses: actions/cache@v4
|
|
55
|
+
id: npm-cache
|
|
56
|
+
with:
|
|
57
|
+
path: ~/.npm
|
|
58
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') || github.sha }}
|
|
59
|
+
|
|
60
|
+
- name: Confirm cache restored correctly
|
|
61
|
+
if: steps.npm-cache.outputs.cache-hit == 'true'
|
|
62
|
+
run: |
|
|
63
|
+
if [ ! -d ~/.npm ]; then
|
|
64
|
+
echo "Cache hit claimed but directory missing — likely empty-key collision"
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
prevention:
|
|
68
|
+
- 'Always add || github.sha fallback after hashFiles() in cache keys'
|
|
69
|
+
- 'Use actions built-in caching (setup-node cache: npm) which handles missing lock files safely'
|
|
70
|
+
- 'In monorepos without a root-level lock file, construct keys from per-package hash patterns'
|
|
71
|
+
- 'Test cache behavior in branches or forks where lock files might not yet exist'
|
|
72
|
+
docs:
|
|
73
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#hashfiles'
|
|
74
|
+
label: 'GitHub Docs: hashFiles() function'
|
|
75
|
+
- url: 'https://github.com/actions/cache/issues/1175'
|
|
76
|
+
label: 'actions/cache#1175: hashFiles empty result causes key collision'
|
|
77
|
+
- url: 'https://github.com/actions/toolkit/blob/main/packages/glob/README.md'
|
|
78
|
+
label: 'actions/toolkit: glob — hashFiles behavior on no match'
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
id: 'caching-artifacts-027'
|
|
2
|
+
title: 'restore-keys fallback matches cross-OS or cross-architecture cache, restoring incompatible binaries'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- restore-keys
|
|
8
|
+
- cross-platform
|
|
9
|
+
- architecture
|
|
10
|
+
- arm64
|
|
11
|
+
- runner-os
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'restore-keys:'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- 'Cache restored from key'
|
|
17
|
+
- 'Exec format error'
|
|
18
|
+
- 'cannot execute binary file: Exec format error'
|
|
19
|
+
root_cause: |
|
|
20
|
+
restore-keys performs prefix matching against ALL cached entries in the repository,
|
|
21
|
+
regardless of operating system or CPU architecture. When a restore-keys prefix is
|
|
22
|
+
shorter than the primary cache key and omits runner.os or runner.arch, a cache
|
|
23
|
+
saved on one platform can be silently restored on a different one.
|
|
24
|
+
|
|
25
|
+
Example: primary key Linux-x64-node-abc123, restore-keys Linux-node- will match
|
|
26
|
+
a Linux ARM64 cache saved as Linux-arm64-node-xyz789. The ARM64 node_modules
|
|
27
|
+
contains native addon binaries (esbuild, sqlite3, etc.) compiled for ARM64; when
|
|
28
|
+
restored on an x64 runner, they fail at runtime with "Exec format error."
|
|
29
|
+
|
|
30
|
+
This became a widespread issue after GitHub introduced macOS ARM64 (M1/M2) runners
|
|
31
|
+
in 2023 and Linux ARM64 runners in 2024. Teams adding new runner architectures
|
|
32
|
+
to existing matrix builds often expose this silently.
|
|
33
|
+
|
|
34
|
+
The cache-hit output evaluates to 'true' even for cross-architecture restores,
|
|
35
|
+
providing no indication that the restored content may be incompatible.
|
|
36
|
+
fix: |
|
|
37
|
+
Always include runner.os AND runner.arch in every level of restore-keys, mirroring
|
|
38
|
+
whatever isolation is present in the primary cache key. No restore-keys prefix should
|
|
39
|
+
ever be shorter than the architecture scope of the primary key.
|
|
40
|
+
fix_code:
|
|
41
|
+
- language: yaml
|
|
42
|
+
label: 'Include runner.os and runner.arch in all restore-keys levels'
|
|
43
|
+
code: |
|
|
44
|
+
- uses: actions/cache@v4
|
|
45
|
+
with:
|
|
46
|
+
path: ~/.npm
|
|
47
|
+
# Primary key includes full OS and architecture isolation
|
|
48
|
+
key: ${{ runner.os }}-${{ runner.arch }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
49
|
+
# Every fallback level maintains OS plus architecture isolation
|
|
50
|
+
restore-keys: |
|
|
51
|
+
${{ runner.os }}-${{ runner.arch }}-node-
|
|
52
|
+
${{ runner.os }}-${{ runner.arch }}-
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: 'Matrix build with per-arch cache keys'
|
|
55
|
+
code: |
|
|
56
|
+
strategy:
|
|
57
|
+
matrix:
|
|
58
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
59
|
+
arch: [x64, arm64]
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/cache@v4
|
|
62
|
+
with:
|
|
63
|
+
path: |
|
|
64
|
+
~/.cargo/registry
|
|
65
|
+
target/
|
|
66
|
+
key: ${{ matrix.os }}-${{ matrix.arch }}-rust-${{ hashFiles('**/Cargo.lock') }}
|
|
67
|
+
restore-keys: |
|
|
68
|
+
${{ matrix.os }}-${{ matrix.arch }}-rust-
|
|
69
|
+
prevention:
|
|
70
|
+
- 'Always include runner.os AND runner.arch in every level of restore-keys'
|
|
71
|
+
- 'Audit cache configurations when adding new runner OS or arch combinations to matrix builds'
|
|
72
|
+
- 'Add a verification step after cache restore to confirm a native binary executes correctly'
|
|
73
|
+
- 'When migrating from x64-only to multi-arch, update all restore-keys at the same time'
|
|
74
|
+
docs:
|
|
75
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#matching-a-cache-key'
|
|
76
|
+
label: 'GitHub Docs: Matching a cache key'
|
|
77
|
+
- url: 'https://github.com/actions/cache#inputs'
|
|
78
|
+
label: 'actions/cache README: restore-keys input'
|
|
79
|
+
- url: 'https://github.com/actions/cache/issues/1660'
|
|
80
|
+
label: 'actions/cache#1660: restore-keys cross-architecture match'
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
id: 'concurrency-timing-023'
|
|
2
|
+
title: 'Cleanup jobs with if: cancelled() do not run when workflow is canceled by concurrency group'
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- concurrency
|
|
7
|
+
- cancelled
|
|
8
|
+
- cleanup
|
|
9
|
+
- cancel-in-progress
|
|
10
|
+
- if-condition
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'cancel-in-progress:\s*true'
|
|
13
|
+
flags: 'i'
|
|
14
|
+
- regex: 'if:\s*cancelled\(\)'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
error_messages:
|
|
17
|
+
- 'This run has been cancelled.'
|
|
18
|
+
- 'Job cancelled by a newer workflow run'
|
|
19
|
+
root_cause: |
|
|
20
|
+
When cancel-in-progress: true cancels a workflow run because a new run was queued in the same
|
|
21
|
+
concurrency group, GitHub cancels the entire workflow run at the infrastructure level before
|
|
22
|
+
individual job-level if: conditions are evaluated. As a result, jobs with if: cancelled() or
|
|
23
|
+
if: always() defined to run after a canceled parent job are themselves canceled before they
|
|
24
|
+
can be dispatched to a runner.
|
|
25
|
+
|
|
26
|
+
This is distinct from a job failing or being manually canceled: concurrency-group cancellation
|
|
27
|
+
is an external platform signal. In practice, cleanup jobs that rely on if: cancelled() may
|
|
28
|
+
start briefly but are killed mid-execution if they happen to be in-flight when the cancel
|
|
29
|
+
propagates.
|
|
30
|
+
fix: |
|
|
31
|
+
Use a separate workflow triggered by workflow_run with types: [completed] as the cleanup
|
|
32
|
+
trigger rather than relying on in-workflow if: cancelled() jobs. The workflow_run approach
|
|
33
|
+
fires reliably regardless of how the parent workflow ended.
|
|
34
|
+
|
|
35
|
+
If the in-workflow approach is required, use if: always() rather than if: cancelled() and
|
|
36
|
+
ensure the cleanup job starts quickly (lightweight first step) to reduce the window during
|
|
37
|
+
which the cancellation signal can reach it.
|
|
38
|
+
fix_code:
|
|
39
|
+
- language: yaml
|
|
40
|
+
label: 'Reliable cleanup via separate workflow_run trigger'
|
|
41
|
+
code: |
|
|
42
|
+
# .github/workflows/cleanup.yml
|
|
43
|
+
on:
|
|
44
|
+
workflow_run:
|
|
45
|
+
workflows: ['CI']
|
|
46
|
+
types: [completed]
|
|
47
|
+
|
|
48
|
+
jobs:
|
|
49
|
+
cleanup:
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
if: >-
|
|
52
|
+
${{ github.event.workflow_run.conclusion == 'cancelled' ||
|
|
53
|
+
github.event.workflow_run.conclusion == 'failure' }}
|
|
54
|
+
steps:
|
|
55
|
+
- name: Run cleanup
|
|
56
|
+
run: echo "Cleaning up after ${{ github.event.workflow_run.conclusion }}"
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: 'Best-effort if:always() with fast first step'
|
|
59
|
+
code: |
|
|
60
|
+
jobs:
|
|
61
|
+
build:
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
- run: ./run-tests.sh
|
|
65
|
+
|
|
66
|
+
cleanup:
|
|
67
|
+
needs: build
|
|
68
|
+
if: always()
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
steps:
|
|
71
|
+
- name: Signal start immediately
|
|
72
|
+
run: echo "Cleanup starting"
|
|
73
|
+
- name: Do cleanup
|
|
74
|
+
run: ./cleanup.sh
|
|
75
|
+
prevention:
|
|
76
|
+
- 'Do not rely solely on if: cancelled() for critical cleanup when cancel-in-progress: true is active'
|
|
77
|
+
- 'Use a separate workflow_run: completed trigger for guaranteed post-run cleanup logic'
|
|
78
|
+
- 'Use if: always() instead of if: cancelled() for broader coverage'
|
|
79
|
+
- 'Keep cleanup steps inside the main job where possible — step-level if: always() is more reliable than job-level when concurrency cancels the run'
|
|
80
|
+
docs:
|
|
81
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-concurrency'
|
|
82
|
+
label: 'GitHub Docs: Using concurrency'
|
|
83
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run'
|
|
84
|
+
label: 'GitHub Docs: workflow_run event'
|
|
85
|
+
- url: 'https://github.com/orgs/community/discussions/13655'
|
|
86
|
+
label: 'GitHub Community: cleanup jobs not running after concurrency cancellation'
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
id: 'concurrency-timing-024'
|
|
2
|
+
title: 'timeout-minutes applies to job execution only, not queue wait time — jobs can wait indefinitely'
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- timeout
|
|
7
|
+
- queue
|
|
8
|
+
- self-hosted
|
|
9
|
+
- runner
|
|
10
|
+
- wait-time
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'timeout-minutes:\s*\d+'
|
|
13
|
+
flags: 'i'
|
|
14
|
+
error_messages:
|
|
15
|
+
- 'The job running on runner has exceeded the maximum execution time of'
|
|
16
|
+
- 'The operation was canceled.'
|
|
17
|
+
root_cause: |
|
|
18
|
+
timeout-minutes only counts elapsed time from when a job actually begins executing on
|
|
19
|
+
a runner — not from when it enters the queue. A job waiting for an available runner
|
|
20
|
+
slot (including jobs waiting in a concurrency group queue) can sit pending for hours
|
|
21
|
+
or indefinitely without any timeout being applied.
|
|
22
|
+
|
|
23
|
+
This is particularly impactful with:
|
|
24
|
+
- Self-hosted runners under heavy load with limited runner capacity
|
|
25
|
+
- Concurrency groups with cancel-in-progress: false that accumulate queued jobs
|
|
26
|
+
- Repository-level runner quotas on GitHub-hosted runners during peak usage
|
|
27
|
+
|
|
28
|
+
Developers are often surprised that a job with timeout-minutes: 30 waited 4+ hours
|
|
29
|
+
before starting, then proceeded to run for its full 30-minute budget.
|
|
30
|
+
fix: |
|
|
31
|
+
There is no native queue-timeout setting in GitHub Actions. Recommended workarounds:
|
|
32
|
+
|
|
33
|
+
1. Set cancel-in-progress: true in concurrency groups to drop stale queued jobs
|
|
34
|
+
when newer commits arrive, preventing queue accumulation.
|
|
35
|
+
2. Monitor queue depth using the GitHub REST API /repos/{owner}/{repo}/actions/runs
|
|
36
|
+
and set up external alerting for runs stuck in 'queued' status too long.
|
|
37
|
+
3. Ensure adequate self-hosted runner pool capacity relative to expected parallelism.
|
|
38
|
+
4. Use github-hosted runners for time-sensitive jobs to avoid self-hosted queue depth issues.
|
|
39
|
+
fix_code:
|
|
40
|
+
- language: yaml
|
|
41
|
+
label: 'Prevent queue accumulation with cancel-in-progress'
|
|
42
|
+
code: |
|
|
43
|
+
jobs:
|
|
44
|
+
build:
|
|
45
|
+
runs-on: self-hosted
|
|
46
|
+
timeout-minutes: 30 # Only counts execution time, NOT queue wait time
|
|
47
|
+
concurrency:
|
|
48
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
49
|
+
cancel-in-progress: true # Drop stale queued jobs on new push
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/checkout@v4
|
|
52
|
+
- run: ./build.sh
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: 'External queue monitoring via API'
|
|
55
|
+
code: |
|
|
56
|
+
# Monitor for stuck queued runs via GitHub API
|
|
57
|
+
# GET /repos/{owner}/{repo}/actions/runs?status=queued
|
|
58
|
+
# Alert if any run has been queued for more than N minutes
|
|
59
|
+
# (implement in a separate monitoring workflow or external system)
|
|
60
|
+
prevention:
|
|
61
|
+
- 'Do not assume timeout-minutes prevents jobs from waiting indefinitely in the runner queue'
|
|
62
|
+
- 'Use cancel-in-progress: true for CI workflows to prevent queue accumulation'
|
|
63
|
+
- 'Size self-hosted runner pools to handle expected peak concurrency'
|
|
64
|
+
- 'Monitor workflow run queue depth separately via the GitHub REST API'
|
|
65
|
+
- 'Document queue wait behavior in team CI runbooks so on-call engineers know what to expect'
|
|
66
|
+
docs:
|
|
67
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idtimeout-minutes'
|
|
68
|
+
label: 'GitHub Docs: timeout-minutes'
|
|
69
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-concurrency'
|
|
70
|
+
label: 'GitHub Docs: Using concurrency'
|
|
71
|
+
- url: 'https://docs.github.com/en/rest/actions/workflow-runs'
|
|
72
|
+
label: 'GitHub REST API: Workflow runs'
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
id: runner-environment-080
|
|
2
|
+
title: "Container job ENTRYPOINT silently overridden by GitHub Actions runner"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- container
|
|
7
|
+
- docker
|
|
8
|
+
- entrypoint
|
|
9
|
+
- job-container
|
|
10
|
+
- dockerfile
|
|
11
|
+
- initialization
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'ENTRYPOINT.*not.*execut|entrypoint.*not.*run|entrypoint.*overrid|custom.*entrypoint.*skip'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'container.*init.*fail|setup.*script.*not.*run'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Custom initialization script did not run in container job"
|
|
19
|
+
- "Expected environment setup from ENTRYPOINT was missing"
|
|
20
|
+
- "Docker container ENTRYPOINT not executing in GitHub Actions"
|
|
21
|
+
root_cause: |
|
|
22
|
+
When a workflow job uses a container image via the container: key, the GitHub
|
|
23
|
+
Actions runner creates the container with --entrypoint "", explicitly replacing
|
|
24
|
+
any ENTRYPOINT defined in the Docker image with an empty string. This is by
|
|
25
|
+
design: the runner must inject its own workspace setup and process management
|
|
26
|
+
before any user steps run.
|
|
27
|
+
|
|
28
|
+
As a result, ENTRYPOINT instructions in the container Dockerfile are silently
|
|
29
|
+
ignored. Steps then run via docker exec into the already-running container, so
|
|
30
|
+
the ENTRYPOINT never fires. Initialization logic that developers expect to run
|
|
31
|
+
(environment setup, tool configuration, user switching) simply does not happen.
|
|
32
|
+
|
|
33
|
+
This is distinct from Docker container ACTIONS (action.yml with runs.using:
|
|
34
|
+
docker), which DO execute ENTRYPOINT as specified. The override only applies to
|
|
35
|
+
workflow job containers (jobs.<id>.container:).
|
|
36
|
+
fix: |
|
|
37
|
+
Choose one of these approaches:
|
|
38
|
+
|
|
39
|
+
1. Move initialization to CMD. GitHub Actions does not override CMD, and the
|
|
40
|
+
docker container options: field lets you pass --entrypoint pointing to your
|
|
41
|
+
init script.
|
|
42
|
+
|
|
43
|
+
2. Use the container options: field to specify the entrypoint explicitly:
|
|
44
|
+
options: --entrypoint /path/to/init.sh
|
|
45
|
+
|
|
46
|
+
3. Add an explicit initialization step at the top of the job. This is the most
|
|
47
|
+
transparent approach: the step runs before other steps and is visible in logs.
|
|
48
|
+
|
|
49
|
+
4. Switch to a Docker container ACTION (action.yml) if you need ENTRYPOINT
|
|
50
|
+
semantics and own the action definition.
|
|
51
|
+
fix_code:
|
|
52
|
+
- language: yaml
|
|
53
|
+
label: "Option A: Override entrypoint via container options"
|
|
54
|
+
code: |
|
|
55
|
+
jobs:
|
|
56
|
+
build:
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
container:
|
|
59
|
+
image: my-custom-image:latest
|
|
60
|
+
options: --entrypoint /usr/local/bin/my-init.sh
|
|
61
|
+
steps:
|
|
62
|
+
- name: Run build
|
|
63
|
+
run: make build
|
|
64
|
+
|
|
65
|
+
- language: yaml
|
|
66
|
+
label: "Option B: Run initialization as an explicit first step (most transparent)"
|
|
67
|
+
code: |
|
|
68
|
+
jobs:
|
|
69
|
+
build:
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
container:
|
|
72
|
+
image: my-custom-image:latest
|
|
73
|
+
steps:
|
|
74
|
+
- name: Initialize environment
|
|
75
|
+
run: /usr/local/bin/my-init.sh
|
|
76
|
+
- name: Run build
|
|
77
|
+
run: make build
|
|
78
|
+
prevention:
|
|
79
|
+
- "Do not rely on ENTRYPOINT for initialization in workflow job containers"
|
|
80
|
+
- "Use CMD for default command arguments; keep setup in workflow steps or the options: field"
|
|
81
|
+
- "Test container behavior locally with docker run --entrypoint '' myimage bash to simulate GitHub Actions"
|
|
82
|
+
- "Use Docker container actions (action.yml with runs.using: docker) when ENTRYPOINT execution is required"
|
|
83
|
+
- "Document any ENTRYPOINT logic removal in a comment in the Dockerfile"
|
|
84
|
+
docs:
|
|
85
|
+
- url: "https://docs.github.com/en/actions/reference/dockerfile-support-for-github-actions"
|
|
86
|
+
label: "Dockerfile support for GitHub Actions"
|
|
87
|
+
- url: "https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container"
|
|
88
|
+
label: "Running jobs in a container"
|
|
89
|
+
- url: "https://github.com/actions/runner/issues/1964"
|
|
90
|
+
label: "actions/runner#1964: Docker entrypoint not executing in container jobs (open, 8 reactions)"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
id: runner-environment-081
|
|
2
|
+
title: "GITHUB_PATH prepend breaks container job when image has no PATH environment variable"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- container
|
|
7
|
+
- GITHUB_PATH
|
|
8
|
+
- PATH
|
|
9
|
+
- docker
|
|
10
|
+
- minimal-image
|
|
11
|
+
- distroless
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'PATH=:[^:"\s]|PATH=\s*:'
|
|
14
|
+
flags: ''
|
|
15
|
+
- regex: '(bash|sh|python|node|java).*not found|exec.*No such file|cannot execute binary'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "bash: command not found"
|
|
19
|
+
- "sh: 1: /usr/local/bin/bash: not found"
|
|
20
|
+
- "exec: /bin/sh: no such file or directory"
|
|
21
|
+
- "OCI runtime exec failed: exec failed: unable to start container process"
|
|
22
|
+
- "/bin/bash: No such file or directory"
|
|
23
|
+
root_cause: |
|
|
24
|
+
When a workflow step uses echo /new/path >> $GITHUB_PATH to add a directory to
|
|
25
|
+
PATH in a container job, the Actions runner prepends the new path to the
|
|
26
|
+
container's existing PATH. If the container image was built without an explicit
|
|
27
|
+
ENV PATH=... Dockerfile instruction, the container has no PATH environment
|
|
28
|
+
variable at startup.
|
|
29
|
+
|
|
30
|
+
The runner's prepend operation results in PATH=:/new/path — a leading colon
|
|
31
|
+
that causes the shell to treat an empty string as the first search directory.
|
|
32
|
+
Worse, standard system paths (/usr/bin, /bin, etc.) are no longer in PATH at
|
|
33
|
+
all, so subsequent steps cannot find any commands including bash itself.
|
|
34
|
+
|
|
35
|
+
Affected images include OpenSUSE, Alpine-based images that inherit from scratch,
|
|
36
|
+
distroless containers, and any custom image that does not explicitly set PATH.
|
|
37
|
+
The issue was reported in actions/runner#3210 and closed as not_planned — the
|
|
38
|
+
runner will not fix this behavior; images must include PATH in their ENV.
|
|
39
|
+
fix: |
|
|
40
|
+
Add an explicit ENV PATH= instruction to the container Dockerfile. This gives
|
|
41
|
+
the runner a non-empty base PATH to prepend to, preserving the standard system
|
|
42
|
+
paths.
|
|
43
|
+
|
|
44
|
+
If you cannot modify the container image, set PATH in a step before any
|
|
45
|
+
GITHUB_PATH usage, or use absolute executable paths in subsequent steps.
|
|
46
|
+
fix_code:
|
|
47
|
+
- language: yaml
|
|
48
|
+
label: "Fix 1: Add ENV PATH to Dockerfile (recommended)"
|
|
49
|
+
code: |
|
|
50
|
+
# Dockerfile fix — add explicit PATH before your custom layers
|
|
51
|
+
FROM opensuse/leap:15.5
|
|
52
|
+
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
53
|
+
|
|
54
|
+
- language: yaml
|
|
55
|
+
label: "Fix 2: Set PATH in a workflow step before adding to GITHUB_PATH"
|
|
56
|
+
code: |
|
|
57
|
+
jobs:
|
|
58
|
+
build:
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
container: opensuse/leap
|
|
61
|
+
steps:
|
|
62
|
+
- name: Initialize PATH for container
|
|
63
|
+
run: |
|
|
64
|
+
# Set base PATH first so prepend works correctly
|
|
65
|
+
echo "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/my/tool" >> $GITHUB_PATH
|
|
66
|
+
- name: Use tool
|
|
67
|
+
run: my-tool --version
|
|
68
|
+
prevention:
|
|
69
|
+
- "Always add ENV PATH=... to custom Docker images used in GitHub Actions container jobs"
|
|
70
|
+
- "Test container images with docker run --entrypoint '' myimage bash -c 'echo $PATH' to verify PATH is set"
|
|
71
|
+
- "Prefer official base images (debian, ubuntu, alpine) that include PATH in their default ENV"
|
|
72
|
+
- "Avoid echo path >> $GITHUB_PATH in minimal, scratch-based, or distroless container images"
|
|
73
|
+
- "Pin to base image versions that explicitly document their ENV PATH value"
|
|
74
|
+
docs:
|
|
75
|
+
- url: "https://github.com/actions/runner/issues/3210"
|
|
76
|
+
label: "actions/runner#3210: Prepending PATH breaks container if image has no PATH env (closed not_planned)"
|
|
77
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#adding-a-system-path"
|
|
78
|
+
label: "Adding a system path — Workflow commands for GitHub Actions"
|
|
79
|
+
- url: "https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container"
|
|
80
|
+
label: "Running jobs in a container"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
id: silent-failures-035
|
|
2
|
+
title: "github.event.inputs empty in workflow_call context — use inputs context instead"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow_call
|
|
7
|
+
- workflow_dispatch
|
|
8
|
+
- inputs-context
|
|
9
|
+
- reusable-workflow
|
|
10
|
+
- dual-trigger
|
|
11
|
+
- event-inputs
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'github\.event\.inputs\.[a-zA-Z_][a-zA-Z0-9_]*'
|
|
14
|
+
flags: ''
|
|
15
|
+
error_messages:
|
|
16
|
+
- "Input parameter evaluated to empty string"
|
|
17
|
+
- "Expected value from github.event.inputs but got empty"
|
|
18
|
+
- "Workflow input undefined in reusable call context"
|
|
19
|
+
root_cause: |
|
|
20
|
+
github.event.inputs is only populated when a workflow is triggered by a
|
|
21
|
+
workflow_dispatch event. When the same workflow is triggered as a reusable
|
|
22
|
+
workflow via workflow_call, github.event.inputs is an empty object {} — all
|
|
23
|
+
property lookups silently return empty string.
|
|
24
|
+
|
|
25
|
+
This is a frequent silent failure in dual-trigger workflows designed to be both
|
|
26
|
+
manually triggered and called as reusable workflows. Developers who write
|
|
27
|
+
${{ github.event.inputs.MY_PARAM }} based on older tutorials or pre-2022 docs
|
|
28
|
+
find that the workflow_call path silently gets empty values, causing wrong
|
|
29
|
+
conditionals, empty environment variables, or unintended default behaviors.
|
|
30
|
+
|
|
31
|
+
In June 2022, GitHub introduced the unified inputs context that works for both
|
|
32
|
+
workflow_dispatch and workflow_call. Using ${{ inputs.PARAM }} is the correct
|
|
33
|
+
cross-trigger approach. github.event.inputs is preserved only for backwards
|
|
34
|
+
compatibility with pure workflow_dispatch workflows.
|
|
35
|
+
fix: |
|
|
36
|
+
Replace all instances of ${{ github.event.inputs.PARAM }} with
|
|
37
|
+
${{ inputs.PARAM }}. The inputs context resolves correctly for both
|
|
38
|
+
workflow_dispatch and workflow_call triggers since the June 2022 unification.
|
|
39
|
+
|
|
40
|
+
Declare the same input names in both workflow_dispatch.inputs and
|
|
41
|
+
workflow_call.inputs sections of the dual-trigger workflow.
|
|
42
|
+
fix_code:
|
|
43
|
+
- language: yaml
|
|
44
|
+
label: "Dual-trigger workflow using inputs context (correct)"
|
|
45
|
+
code: |
|
|
46
|
+
on:
|
|
47
|
+
workflow_dispatch:
|
|
48
|
+
inputs:
|
|
49
|
+
environment:
|
|
50
|
+
description: 'Target environment'
|
|
51
|
+
required: true
|
|
52
|
+
type: string
|
|
53
|
+
workflow_call:
|
|
54
|
+
inputs:
|
|
55
|
+
environment:
|
|
56
|
+
description: 'Target environment'
|
|
57
|
+
required: true
|
|
58
|
+
type: string
|
|
59
|
+
|
|
60
|
+
jobs:
|
|
61
|
+
deploy:
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
# Correct: inputs context works for both workflow_dispatch and workflow_call
|
|
65
|
+
- name: Deploy
|
|
66
|
+
run: echo "Deploying to ${{ inputs.environment }}"
|
|
67
|
+
|
|
68
|
+
# Wrong: github.event.inputs is always empty on workflow_call
|
|
69
|
+
# - run: echo "Deploying to ${{ github.event.inputs.environment }}"
|
|
70
|
+
prevention:
|
|
71
|
+
- "Always use ${{ inputs.PARAM }} in reusable or dual-trigger workflows"
|
|
72
|
+
- "Never use github.event.inputs in any workflow that may be triggered via workflow_call"
|
|
73
|
+
- "Search for github.event.inputs when converting a standalone workflow to also support workflow_call"
|
|
74
|
+
- "Run the workflow via workflow_call in CI to catch empty-input failures early"
|
|
75
|
+
docs:
|
|
76
|
+
- url: "https://github.blog/changelog/2022-06-09-github-actions-inputs-unified-across-manual-and-reusable-workflows/"
|
|
77
|
+
label: "GitHub Changelog: Inputs unified across manual and reusable workflows"
|
|
78
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#inputs-context"
|
|
79
|
+
label: "Inputs context reference — GitHub Docs"
|
|
80
|
+
- url: "https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows"
|
|
81
|
+
label: "Reusing workflows — GitHub Docs"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
id: triggers-025
|
|
2
|
+
title: "workflow_dispatch Run Workflow button missing — file must exist in default branch"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow_dispatch
|
|
7
|
+
- manual-trigger
|
|
8
|
+
- default-branch
|
|
9
|
+
- actions-tab
|
|
10
|
+
- ui
|
|
11
|
+
- run-workflow-button
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'workflow.*dispatch.*not.*show|run workflow.*button.*miss|workflow.*not.*appear.*action'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'workflow_dispatch.*feature.*branch|dispatch.*not.*available'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Run workflow button not visible in Actions tab"
|
|
19
|
+
- "workflow_dispatch workflow not appearing in Actions"
|
|
20
|
+
- "Cannot trigger workflow manually — Run workflow button missing"
|
|
21
|
+
- "Workflow with workflow_dispatch not showing in GitHub UI"
|
|
22
|
+
root_cause: |
|
|
23
|
+
For a workflow with on: workflow_dispatch to appear in the GitHub Actions tab
|
|
24
|
+
with a Run workflow button, the workflow file must exist in the repository default
|
|
25
|
+
branch (typically main or master). GitHub reads the list of available manual
|
|
26
|
+
workflows exclusively from the default branch at page load time.
|
|
27
|
+
|
|
28
|
+
Common scenarios that cause the button to be missing:
|
|
29
|
+
- Adding workflow_dispatch to a workflow file only in a feature branch, before
|
|
30
|
+
merging to the default branch
|
|
31
|
+
- Creating a new workflow in a PR that has not yet merged
|
|
32
|
+
- Renaming the default branch without updating any cached references
|
|
33
|
+
- Working in a fork where the default branch differs from upstream
|
|
34
|
+
|
|
35
|
+
Once the workflow file exists in the default branch, the Run workflow button
|
|
36
|
+
appears and you can select any branch to run it against using the branch
|
|
37
|
+
dropdown. The trigger definition must be in the default branch; the execution
|
|
38
|
+
can target any branch.
|
|
39
|
+
|
|
40
|
+
This is the same constraint as on: schedule — both triggers only activate from
|
|
41
|
+
the default branch. The constraint is by design to prevent security issues from
|
|
42
|
+
untrusted branch code running with elevated permissions.
|
|
43
|
+
fix: |
|
|
44
|
+
Merge the workflow file containing on: workflow_dispatch to the default branch.
|
|
45
|
+
The Run workflow button appears immediately after the merge.
|
|
46
|
+
|
|
47
|
+
For testing before merge, trigger the workflow via the GitHub API or CLI, which
|
|
48
|
+
does not require the UI button and can target any branch.
|
|
49
|
+
fix_code:
|
|
50
|
+
- language: yaml
|
|
51
|
+
label: "Workflow file structure — must be in default branch for UI button"
|
|
52
|
+
code: |
|
|
53
|
+
# .github/workflows/deploy.yml — merge this to main/default branch first
|
|
54
|
+
name: Deploy
|
|
55
|
+
on:
|
|
56
|
+
workflow_dispatch:
|
|
57
|
+
inputs:
|
|
58
|
+
environment:
|
|
59
|
+
description: 'Target environment'
|
|
60
|
+
type: choice
|
|
61
|
+
options:
|
|
62
|
+
- staging
|
|
63
|
+
- production
|
|
64
|
+
required: true
|
|
65
|
+
|
|
66
|
+
jobs:
|
|
67
|
+
deploy:
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/checkout@v4
|
|
71
|
+
- name: Deploy
|
|
72
|
+
run: echo "Deploying to ${{ inputs.environment }}"
|
|
73
|
+
|
|
74
|
+
- language: yaml
|
|
75
|
+
label: "Trigger via CLI during feature branch development (no UI button needed)"
|
|
76
|
+
code: |
|
|
77
|
+
# Trigger from feature branch while workflow file is still in that branch:
|
|
78
|
+
# gh workflow run deploy.yml --ref feature/my-branch -f environment=staging
|
|
79
|
+
#
|
|
80
|
+
# This works even before the workflow is merged to the default branch.
|
|
81
|
+
# Replace 'deploy.yml' with your workflow filename.
|
|
82
|
+
prevention:
|
|
83
|
+
- "Merge workflow files to the default branch before relying on the Run workflow UI button"
|
|
84
|
+
- "Use the GitHub CLI to trigger workflows during development before merging"
|
|
85
|
+
- "Remember: the trigger definition must be in the default branch; the run can target any branch"
|
|
86
|
+
- "Check Settings > Branches to confirm which branch is the repository default branch"
|
|
87
|
+
- "Use a draft PR to stage the workflow file while keeping it reviewable before merge"
|
|
88
|
+
docs:
|
|
89
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_dispatch"
|
|
90
|
+
label: "workflow_dispatch — Events that trigger workflows"
|
|
91
|
+
- url: "https://stackoverflow.com/questions/67523882/workflow-is-not-shown-so-i-cannot-run-it-manually-github-actions"
|
|
92
|
+
label: "Stack Overflow: Workflow not shown so I cannot run it manually (53 votes, 63K views)"
|
|
93
|
+
- url: "https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow"
|
|
94
|
+
label: "Manually running a workflow — GitHub Docs"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
id: 'triggers-026'
|
|
2
|
+
title: 'workflow_run does not fire when triggering workflow is skipped by paths or branches filter'
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow-run
|
|
7
|
+
- skipped
|
|
8
|
+
- paths-filter
|
|
9
|
+
- branches-filter
|
|
10
|
+
- trigger-chain
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'workflow_run'
|
|
13
|
+
flags: 'i'
|
|
14
|
+
- regex: 'types:\s*\[.*completed.*\]'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
error_messages:
|
|
17
|
+
- 'This run was triggered by a workflow_run event but the parent workflow was not found'
|
|
18
|
+
root_cause: |
|
|
19
|
+
When a workflow is skipped because its on.push.paths or on.push.branches filter does not
|
|
20
|
+
match the pushed commit, GitHub does not create a workflow run record and therefore does
|
|
21
|
+
not emit a workflow_run completion event. A downstream workflow that listens for
|
|
22
|
+
on.workflow_run: [UpstreamWorkflow] with types: [completed] silently never fires.
|
|
23
|
+
|
|
24
|
+
This breaks fan-out CI/CD architectures where a primary workflow is gated by path/branch
|
|
25
|
+
filters, and secondary workflows (deploy, notify, publish) depend on its completion.
|
|
26
|
+
When the paths filter causes the primary workflow to be skipped entirely, the downstream
|
|
27
|
+
chain is dropped with no error message.
|
|
28
|
+
|
|
29
|
+
The issue affects both on.push.paths and on.push.branches filters. It does not affect
|
|
30
|
+
workflows that run but exit early via an if: condition on a job — only skipped runs
|
|
31
|
+
(which never appear in the GitHub Actions run list) cause the downstream gap.
|
|
32
|
+
fix: |
|
|
33
|
+
Replace trigger-level on.push.paths filtering with in-workflow job-level path detection
|
|
34
|
+
using an action like dorny/paths-filter. This ensures the upstream workflow always
|
|
35
|
+
creates a run (triggering the workflow_run event), while individual jobs are skipped
|
|
36
|
+
when paths do not match.
|
|
37
|
+
fix_code:
|
|
38
|
+
- language: yaml
|
|
39
|
+
label: 'Replace trigger-level paths filter with in-workflow detection'
|
|
40
|
+
code: |
|
|
41
|
+
# upstream.yml
|
|
42
|
+
# BAD: on.push.paths silently skips the run — workflow_run downstream never fires
|
|
43
|
+
# on:
|
|
44
|
+
# push:
|
|
45
|
+
# paths: ['src/**']
|
|
46
|
+
|
|
47
|
+
# GOOD: Always run, detect paths inside the workflow
|
|
48
|
+
on: [push]
|
|
49
|
+
|
|
50
|
+
jobs:
|
|
51
|
+
detect-changes:
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
outputs:
|
|
54
|
+
src-changed: ${{ steps.filter.outputs.src }}
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v4
|
|
57
|
+
- uses: dorny/paths-filter@v3
|
|
58
|
+
id: filter
|
|
59
|
+
with:
|
|
60
|
+
filters: |
|
|
61
|
+
src:
|
|
62
|
+
- 'src/**'
|
|
63
|
+
|
|
64
|
+
build:
|
|
65
|
+
needs: detect-changes
|
|
66
|
+
if: ${{ needs.detect-changes.outputs.src-changed == 'true' }}
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
- run: ./build.sh
|
|
71
|
+
|
|
72
|
+
# downstream.yml — now reliably fires on every push
|
|
73
|
+
# on:
|
|
74
|
+
# workflow_run:
|
|
75
|
+
# workflows: ['Upstream CI']
|
|
76
|
+
# types: [completed]
|
|
77
|
+
prevention:
|
|
78
|
+
- 'Do not combine on.push.paths/branches filters with workflow_run downstream dependencies'
|
|
79
|
+
- 'Use dorny/paths-filter or tj-actions/changed-files inside always-running workflows instead'
|
|
80
|
+
- 'Test the full trigger chain end-to-end by pushing commits that both match and do not match the filter'
|
|
81
|
+
- 'Document the skipped-runs gap in team CI docs for anyone building workflow_run chains'
|
|
82
|
+
docs:
|
|
83
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run'
|
|
84
|
+
label: 'GitHub Docs: workflow_run — triggering workflow must run on default branch'
|
|
85
|
+
- url: 'https://github.com/dorny/paths-filter'
|
|
86
|
+
label: 'dorny/paths-filter — job-level path filtering'
|
|
87
|
+
- url: 'https://github.com/orgs/community/discussions/23710'
|
|
88
|
+
label: 'GitHub Community: workflow_run not triggered when upstream workflow is skipped'
|
package/package.json
CHANGED