@htekdev/actions-debugger 1.0.86 → 1.0.88
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/known-unsolved/known-unsolved-051.yml +94 -0
- package/errors/runner-environment/macos-arm64-rosetta-not-installed.yml +97 -0
- package/errors/runner-environment/runner-disk-full-no-space-left.yml +106 -0
- package/errors/runner-environment/runner-environment-151.yml +93 -0
- package/errors/runner-environment/runner-environment-152.yml +90 -0
- package/errors/runner-environment/ubuntu-snap-snapd-not-running.yml +90 -0
- package/errors/runner-environment/ubuntu-systemctl-systemd-not-running.yml +95 -0
- package/errors/silent-failures/silent-failures-080.yml +97 -0
- package/errors/silent-failures/silent-failures-081.yml +86 -0
- package/errors/triggers/triggers-059.yml +106 -0
- package/errors/yaml-syntax/yaml-syntax-054.yml +94 -0
- package/errors/yaml-syntax/yaml-syntax-055.yml +88 -0
- package/package.json +1 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
id: known-unsolved-051
|
|
2
|
+
title: 'No workflow-level `timeout-minutes` — only job-level and step-level timeouts; runaway workflows consume runner hours with no global cap'
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- timeout
|
|
7
|
+
- workflow-level
|
|
8
|
+
- runner-hours
|
|
9
|
+
- billing
|
|
10
|
+
- limitation
|
|
11
|
+
- runaway-workflow
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'timeout.minutes'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'workflow.*timed? ?out|timed? ?out.*workflow'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "No workflow-level timeout — only job-level timeout-minutes is supported"
|
|
19
|
+
- "Unexpected key 'timeout-minutes' at workflow level — property is only valid on jobs"
|
|
20
|
+
root_cause: |
|
|
21
|
+
GitHub Actions supports `timeout-minutes:` only at the JOB level and STEP level.
|
|
22
|
+
There is no `timeout-minutes:` field at the workflow level (the level containing
|
|
23
|
+
`on:`, `env:`, and `jobs:`). If placed at the workflow level, actionlint reports
|
|
24
|
+
"unexpected key 'timeout-minutes'" and the key is silently ignored at runtime
|
|
25
|
+
with no effect on execution.
|
|
26
|
+
|
|
27
|
+
The default job timeout is 360 minutes (6 hours). A workflow with multiple jobs,
|
|
28
|
+
each using the default timeout, can run for many times 360 minutes total. There is
|
|
29
|
+
no built-in way to enforce a global "if this entire workflow hasn't finished in X
|
|
30
|
+
hours, cancel everything."
|
|
31
|
+
|
|
32
|
+
Common scenarios where this causes unexpected runner hour consumption:
|
|
33
|
+
- A self-hosted runner goes offline mid-job; the job hangs until its per-job timeout
|
|
34
|
+
- A workflow waiting on a deployment environment review that is never approved
|
|
35
|
+
- A matrix job with a misconfigured container that hangs on startup
|
|
36
|
+
- A network call in a step that never times out (no step-level timeout set)
|
|
37
|
+
|
|
38
|
+
This is a long-requested GitHub Actions feature with hundreds of community upvotes.
|
|
39
|
+
fix: |
|
|
40
|
+
Workarounds (no official workflow-level timeout exists):
|
|
41
|
+
|
|
42
|
+
1. SET CONSERVATIVE JOB TIMEOUTS: Add explicit `timeout-minutes:` to every job
|
|
43
|
+
with a value appropriate for that job's actual expected runtime plus a buffer.
|
|
44
|
+
Do not rely on the 360-minute default.
|
|
45
|
+
|
|
46
|
+
2. SET STEP-LEVEL TIMEOUTS: Add `timeout-minutes:` to individual steps that perform
|
|
47
|
+
network calls, long-running builds, or any operation that can hang.
|
|
48
|
+
|
|
49
|
+
3. WATCHDOG WORKFLOW: A separate workflow triggered by `workflow_run` can monitor
|
|
50
|
+
in-progress runs and cancel them via the API if they exceed a threshold duration.
|
|
51
|
+
|
|
52
|
+
4. ENVIRONMENT REVIEW EXPIRY: For deployment workflows stuck on required reviewers,
|
|
53
|
+
configure an expiry window under Settings > Environments > [env] >
|
|
54
|
+
Deployment protection rules > Timeout.
|
|
55
|
+
fix_code:
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: 'Set explicit per-job timeouts as a workaround'
|
|
58
|
+
code: |
|
|
59
|
+
jobs:
|
|
60
|
+
build:
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
timeout-minutes: 15 # Set per-job: build should not exceed 15 min
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v4
|
|
65
|
+
- run: npm ci && npm run build
|
|
66
|
+
|
|
67
|
+
test:
|
|
68
|
+
needs: build
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
timeout-minutes: 20 # Integration tests: 20 min max
|
|
71
|
+
steps:
|
|
72
|
+
- name: Run tests
|
|
73
|
+
timeout-minutes: 15 # Step-level: individual test run cap
|
|
74
|
+
run: npm run test:integration
|
|
75
|
+
|
|
76
|
+
deploy:
|
|
77
|
+
needs: test
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
timeout-minutes: 10 # Deployment itself should be fast
|
|
80
|
+
environment: production
|
|
81
|
+
steps:
|
|
82
|
+
- run: ./scripts/deploy.sh
|
|
83
|
+
prevention:
|
|
84
|
+
- 'Always set `timeout-minutes:` on every job — do not rely on the 360-minute default for normal CI workflows.'
|
|
85
|
+
- 'Add `timeout-minutes:` to individual steps that perform network I/O, external API calls, or any blocking operation.'
|
|
86
|
+
- 'Monitor unexpected Actions billing spikes: they often indicate a stuck workflow running against the 6-hour default job timeout.'
|
|
87
|
+
- 'Set deployment environment review expiry windows to prevent workflows from hanging indefinitely awaiting approval.'
|
|
88
|
+
docs:
|
|
89
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idtimeout-minutes'
|
|
90
|
+
label: 'Workflow syntax: jobs.<job_id>.timeout-minutes'
|
|
91
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstepstimeout-minutes'
|
|
92
|
+
label: 'Workflow syntax: jobs.<job_id>.steps[*].timeout-minutes'
|
|
93
|
+
- url: 'https://github.com/orgs/community/discussions/16058'
|
|
94
|
+
label: 'GitHub Community: Workflow-level timeout-minutes (feature request)'
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
id: runner-environment-154
|
|
2
|
+
title: "macOS ARM64 runner: Rosetta 2 not pre-installed — x86_64 binaries fail"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- macos
|
|
7
|
+
- arm64
|
|
8
|
+
- rosetta
|
|
9
|
+
- apple-silicon
|
|
10
|
+
- binary-compatibility
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'Bad CPU type in executable'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'Exec format error'
|
|
15
|
+
flags: i
|
|
16
|
+
- regex: 'is not supported on this machine \(CPU type'
|
|
17
|
+
flags: i
|
|
18
|
+
- regex: 'rosetta.*not.*install|softwareupdate.*rosetta'
|
|
19
|
+
flags: i
|
|
20
|
+
error_messages:
|
|
21
|
+
- "zsh: bad CPU type in executable: /usr/local/bin/mytool"
|
|
22
|
+
- "Exec format error"
|
|
23
|
+
- "/usr/local/bin/node: Bad CPU type in executable"
|
|
24
|
+
- "The file '/path/to/binary' is not supported on this machine (CPU type 16777223)"
|
|
25
|
+
root_cause: |
|
|
26
|
+
GitHub-hosted macOS runners on Apple Silicon hardware (macos-14, macos-15, and the `macos-latest`
|
|
27
|
+
label when pointing to ARM64 images) do NOT have Rosetta 2 pre-installed. Rosetta 2 is Apple's
|
|
28
|
+
x86_64-to-ARM64 translation layer that allows Intel-compiled binaries to run on Apple Silicon.
|
|
29
|
+
|
|
30
|
+
Without Rosetta 2, any x86_64 binary executed on the ARM64 runner fails immediately with
|
|
31
|
+
"Bad CPU type in executable" or "Exec format error". Common sources of x86_64 binaries:
|
|
32
|
+
- Pre-compiled tool downloads targeting darwin-x64
|
|
33
|
+
- npm packages with native addons built for x86_64
|
|
34
|
+
- Homebrew formulae that only have x86_64 bottles
|
|
35
|
+
- Custom build scripts that hardcode `arch -x86_64`
|
|
36
|
+
- Docker images using `linux/amd64` platform
|
|
37
|
+
|
|
38
|
+
This is distinct from Linux ARM64 runners (ubuntu-24.04-arm) where the error manifests
|
|
39
|
+
differently and no Rosetta equivalent exists. On macOS, Rosetta 2 can be installed as a
|
|
40
|
+
one-time setup step.
|
|
41
|
+
fix: |
|
|
42
|
+
Option 1 — Install Rosetta 2 at job start (quick workaround):
|
|
43
|
+
Add `softwareupdate --install-rosetta --agree-to-license` as the first step. This allows
|
|
44
|
+
x86_64 binaries to run via translation.
|
|
45
|
+
|
|
46
|
+
Option 2 — Use ARM64-native binaries (preferred):
|
|
47
|
+
Update download scripts to detect `arch` and fetch the correct binary:
|
|
48
|
+
- Use `$(uname -m)` to branch between arm64 and x86_64 download URLs
|
|
49
|
+
- Use `arch -arm64 brew install` for Homebrew packages
|
|
50
|
+
- For npm: ensure native addons are rebuilt for the current architecture
|
|
51
|
+
|
|
52
|
+
Option 3 — Pin to Intel macOS runner:
|
|
53
|
+
Use `runs-on: macos-13` which is the last Intel-based macOS runner image. Note that
|
|
54
|
+
macos-13 is scheduled for deprecation; this is a temporary workaround only.
|
|
55
|
+
fix_code:
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: "Install Rosetta 2 as first step (workaround)"
|
|
58
|
+
code: |
|
|
59
|
+
jobs:
|
|
60
|
+
build:
|
|
61
|
+
runs-on: macos-latest # ARM64 Apple Silicon
|
|
62
|
+
steps:
|
|
63
|
+
- name: Install Rosetta 2
|
|
64
|
+
run: softwareupdate --install-rosetta --agree-to-license
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
- name: Download tool
|
|
67
|
+
run: |
|
|
68
|
+
# x86_64 binary will now work via Rosetta translation
|
|
69
|
+
curl -L https://example.com/tool-darwin-x64 -o tool
|
|
70
|
+
chmod +x tool
|
|
71
|
+
./tool
|
|
72
|
+
- language: yaml
|
|
73
|
+
label: "Detect architecture and fetch the correct binary (preferred)"
|
|
74
|
+
code: |
|
|
75
|
+
steps:
|
|
76
|
+
- name: Download architecture-appropriate binary
|
|
77
|
+
run: |
|
|
78
|
+
ARCH=$(uname -m)
|
|
79
|
+
if [ "$ARCH" = "arm64" ]; then
|
|
80
|
+
BINARY_URL="https://example.com/tool-darwin-arm64"
|
|
81
|
+
else
|
|
82
|
+
BINARY_URL="https://example.com/tool-darwin-x64"
|
|
83
|
+
fi
|
|
84
|
+
curl -L "$BINARY_URL" -o tool
|
|
85
|
+
chmod +x tool
|
|
86
|
+
./tool
|
|
87
|
+
prevention:
|
|
88
|
+
- "Always test workflows on both `macos-13` (Intel) and `macos-14`+ (ARM64) runners"
|
|
89
|
+
- "Check download scripts for hardcoded `darwin-x64` or `darwin-amd64` architecture strings"
|
|
90
|
+
- "Prefer tools distributed via Homebrew — many formulae now have native ARM64 bottles"
|
|
91
|
+
- "Use `${{ runner.arch }}` in workflow expressions to branch on architecture (`X64` vs `ARM64`)"
|
|
92
|
+
- "Audit npm packages with native addons — they must support `darwin-arm64` or provide a WASM fallback"
|
|
93
|
+
docs:
|
|
94
|
+
- url: "https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources"
|
|
95
|
+
label: "GitHub Docs: Supported runners and hardware resources"
|
|
96
|
+
- url: "https://support.apple.com/en-us/102527"
|
|
97
|
+
label: "Apple: If you need to install Rosetta on your Mac"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
id: runner-environment-155
|
|
2
|
+
title: "GitHub-hosted runner disk full — 'No space left on device' during large builds"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- disk-space
|
|
7
|
+
- ubuntu
|
|
8
|
+
- docker
|
|
9
|
+
- large-builds
|
|
10
|
+
- runner-storage
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'No space left on device'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'write.*error.*28|errno 28'
|
|
15
|
+
flags: i
|
|
16
|
+
- regex: 'disk.*full|filesystem.*full|out of disk'
|
|
17
|
+
flags: i
|
|
18
|
+
- regex: 'no space left'
|
|
19
|
+
flags: i
|
|
20
|
+
error_messages:
|
|
21
|
+
- "No space left on device"
|
|
22
|
+
- "Error: write /var/lib/docker/tmp/...: no space left on device"
|
|
23
|
+
- "tar: ./app: Cannot write: No space left on device"
|
|
24
|
+
- "error: failed to push some refs: No space left on device"
|
|
25
|
+
- "OSError: [Errno 28] No space left on device"
|
|
26
|
+
root_cause: |
|
|
27
|
+
GitHub-hosted Ubuntu runners have approximately 14 GB of usable disk space after the
|
|
28
|
+
pre-installed tool suite occupies the rest. The runner image includes a large set of
|
|
29
|
+
pre-installed software that consumes the majority of available disk:
|
|
30
|
+
- Android SDK: ~8.7 GB
|
|
31
|
+
- .NET SDKs: ~1.5 GB
|
|
32
|
+
- Haskell/GHC: ~5.4 GB
|
|
33
|
+
- CodeQL: ~5.4 GB
|
|
34
|
+
- Docker images cached on the runner: varies
|
|
35
|
+
|
|
36
|
+
Workflows that perform large Docker builds, pull multiple Docker images, build large
|
|
37
|
+
native packages, or accumulate many npm/cargo/maven dependencies can exhaust the
|
|
38
|
+
~14 GB limit. Common triggers:
|
|
39
|
+
- `docker build` for large multi-stage images
|
|
40
|
+
- `docker pull` of several large base images
|
|
41
|
+
- Building and caching large Rust/C++ projects
|
|
42
|
+
- Downloading large ML models or datasets
|
|
43
|
+
- Generating large test fixtures or build artifacts
|
|
44
|
+
|
|
45
|
+
The error manifests in diverse ways: Docker build errors (errno 28), tar extraction
|
|
46
|
+
failures, git push failures writing pack files, or Python/Node crashes writing temp files.
|
|
47
|
+
fix: |
|
|
48
|
+
Free disk space before your build by removing pre-installed tools you don't need.
|
|
49
|
+
The community-maintained `jlumbroso/free-disk-space` action is the standard solution.
|
|
50
|
+
|
|
51
|
+
Manual removal commands for the largest consumers:
|
|
52
|
+
- Android SDK: `sudo rm -rf /usr/local/lib/android`
|
|
53
|
+
- .NET: `sudo rm -rf /usr/share/dotnet`
|
|
54
|
+
- Haskell/GHC: `sudo rm -rf /usr/local/.ghcup`
|
|
55
|
+
- Large packages: `sudo apt-get clean && sudo apt-get autoremove -y`
|
|
56
|
+
- Docker build cache: `docker builder prune -af`
|
|
57
|
+
|
|
58
|
+
Alternatively, upgrade to GitHub-hosted larger runners (4+ cores) which provide
|
|
59
|
+
more disk space, or use self-hosted runners.
|
|
60
|
+
fix_code:
|
|
61
|
+
- language: yaml
|
|
62
|
+
label: "Free disk space before large Docker build"
|
|
63
|
+
code: |
|
|
64
|
+
jobs:
|
|
65
|
+
build:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
steps:
|
|
68
|
+
- name: Free disk space
|
|
69
|
+
uses: jlumbroso/free-disk-space@main
|
|
70
|
+
with:
|
|
71
|
+
tool-cache: false
|
|
72
|
+
android: true
|
|
73
|
+
dotnet: true
|
|
74
|
+
haskell: true
|
|
75
|
+
large-packages: true
|
|
76
|
+
docker-images: true
|
|
77
|
+
swap-storage: true
|
|
78
|
+
- uses: actions/checkout@v4
|
|
79
|
+
- name: Build Docker image
|
|
80
|
+
run: docker build -t myapp:latest .
|
|
81
|
+
- language: yaml
|
|
82
|
+
label: "Manual removal of largest pre-installed tools"
|
|
83
|
+
code: |
|
|
84
|
+
steps:
|
|
85
|
+
- name: Free disk space (manual)
|
|
86
|
+
run: |
|
|
87
|
+
df -h /
|
|
88
|
+
sudo rm -rf /usr/local/lib/android
|
|
89
|
+
sudo rm -rf /usr/share/dotnet
|
|
90
|
+
sudo rm -rf /usr/local/.ghcup
|
|
91
|
+
sudo apt-get clean
|
|
92
|
+
docker builder prune -af || true
|
|
93
|
+
df -h /
|
|
94
|
+
prevention:
|
|
95
|
+
- "Add a disk-space check step (`df -h /`) early in the workflow to detect capacity issues before they cause cryptic failures"
|
|
96
|
+
- "Run `jlumbroso/free-disk-space` before any Docker build or large compilation job"
|
|
97
|
+
- "Remove only tools you know you don't need — removing the tool-cache may break `actions/setup-*` steps"
|
|
98
|
+
- "Consider using `runs-on: ubuntu-latest-4-cores` (GitHub Teams) which provides more disk space"
|
|
99
|
+
- "Use Docker `--no-cache` flags cautiously — reusing layer cache can actually save disk vs rebuilding every layer"
|
|
100
|
+
docs:
|
|
101
|
+
- url: "https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources"
|
|
102
|
+
label: "GitHub Docs: Runner hardware resources"
|
|
103
|
+
- url: "https://github.com/jlumbroso/free-disk-space"
|
|
104
|
+
label: "jlumbroso/free-disk-space action"
|
|
105
|
+
- url: "https://github.com/actions/runner-images/issues/2840"
|
|
106
|
+
label: "runner-images#2840: Tracking disk space usage"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
id: runner-environment-151
|
|
2
|
+
title: "Docker container action creates root-owned files causing Permission denied in following steps"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- docker
|
|
7
|
+
- container-action
|
|
8
|
+
- file-permissions
|
|
9
|
+
- root-user
|
|
10
|
+
- workspace
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'Permission denied'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'EACCES: permission denied'
|
|
15
|
+
flags: i
|
|
16
|
+
- regex: 'cannot open .+ Permission denied'
|
|
17
|
+
flags: i
|
|
18
|
+
- regex: 'error: open .+ permission denied'
|
|
19
|
+
flags: i
|
|
20
|
+
error_messages:
|
|
21
|
+
- "Permission denied"
|
|
22
|
+
- "cannot create directory '...': Permission denied"
|
|
23
|
+
- "EACCES: permission denied, open '/github/workspace/...'"
|
|
24
|
+
- "error: cannot open '.git/COMMIT_EDITMSG': Permission denied"
|
|
25
|
+
- "chown: changing ownership of '...': Operation not permitted"
|
|
26
|
+
root_cause: |
|
|
27
|
+
Docker-based GitHub Actions — both `uses: docker://image:tag` inline steps and
|
|
28
|
+
actions with `runs.using: docker` in their action.yml — run their container process
|
|
29
|
+
as root (UID 0) by default unless the Dockerfile explicitly sets a USER directive.
|
|
30
|
+
|
|
31
|
+
Any files written to `GITHUB_WORKSPACE` (mounted at `/github/workspace` inside the
|
|
32
|
+
container) are created with root ownership (uid=0, gid=0). Subsequent workflow steps
|
|
33
|
+
run as the `runner` user (UID 1001) and cannot read, modify, or delete root-owned
|
|
34
|
+
files, causing "Permission denied" errors.
|
|
35
|
+
|
|
36
|
+
The failure is especially common when:
|
|
37
|
+
- A Docker action writes build artifacts, generated code, or lock files.
|
|
38
|
+
- A subsequent step tries to commit changes or run tools on the generated output.
|
|
39
|
+
- The workflow uses `actions/checkout` after a Docker action and git operations fail.
|
|
40
|
+
fix: |
|
|
41
|
+
Option 1 — Chown the workspace after the Docker action (easiest for third-party actions):
|
|
42
|
+
Add a step after the Docker action: `sudo chown -R "$USER:$(id -gn)" "$GITHUB_WORKSPACE"`
|
|
43
|
+
|
|
44
|
+
Option 2 — Set USER in the Dockerfile (best for actions you control):
|
|
45
|
+
Add `USER 1001` (runner UID) to the action's Dockerfile so all written files
|
|
46
|
+
are owned by the runner user.
|
|
47
|
+
|
|
48
|
+
Option 3 — Use --user flag in the Docker run args:
|
|
49
|
+
Set `args` in the action's action.yml to include `--user=1001:127`.
|
|
50
|
+
fix_code:
|
|
51
|
+
- language: yaml
|
|
52
|
+
label: "Fix: chown workspace after Docker action (works for any Docker-based action)"
|
|
53
|
+
code: |
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/checkout@v4
|
|
56
|
+
|
|
57
|
+
- name: Run Docker-based action
|
|
58
|
+
uses: docker://my-build-image:latest
|
|
59
|
+
with:
|
|
60
|
+
args: '--output /github/workspace/dist'
|
|
61
|
+
|
|
62
|
+
- name: Fix file ownership after Docker action
|
|
63
|
+
run: sudo chown -R "$USER:$(id -gn)" "$GITHUB_WORKSPACE"
|
|
64
|
+
|
|
65
|
+
- name: Use build output (now accessible as runner user)
|
|
66
|
+
run: ls -la dist/ && cat dist/output.txt
|
|
67
|
+
- language: yaml
|
|
68
|
+
label: "Fix: set non-root USER in Dockerfile (preferred when controlling the action)"
|
|
69
|
+
code: |
|
|
70
|
+
# In your Docker action's Dockerfile — run as UID 1001 to match GitHub runner
|
|
71
|
+
# FROM ubuntu:22.04
|
|
72
|
+
# RUN useradd -u 1001 -g 127 runner
|
|
73
|
+
# WORKDIR /github/workspace
|
|
74
|
+
# USER 1001
|
|
75
|
+
# ENTRYPOINT ["/entrypoint.sh"]
|
|
76
|
+
|
|
77
|
+
# With this Dockerfile, no chown step is needed — files are owned by runner user
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/checkout@v4
|
|
80
|
+
- uses: ./ # local Docker action with non-root USER
|
|
81
|
+
- run: cat generated-output.txt # accessible without permission errors
|
|
82
|
+
prevention:
|
|
83
|
+
- "Always set a non-root USER directive in Dockerfiles for actions that write to the workspace."
|
|
84
|
+
- "After any third-party Docker action, add a chown step before accessing created files."
|
|
85
|
+
- "Prefer JavaScript/TypeScript or composite actions over Docker actions when workspace I/O is needed — they run as the runner user by default."
|
|
86
|
+
- "Test Docker actions locally with UID 1001 to catch permission issues before CI."
|
|
87
|
+
docs:
|
|
88
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/creating-actions/dockerfile-support-for-github-actions"
|
|
89
|
+
label: "Dockerfile support for GitHub Actions"
|
|
90
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-docker-container-action"
|
|
91
|
+
label: "Creating a Docker container action"
|
|
92
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables"
|
|
93
|
+
label: "GITHUB_WORKSPACE default environment variable"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
id: runner-environment-152
|
|
2
|
+
title: 'Organization "Require immutable actions" policy blocks tag-pinned action refs — must use full commit SHA'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- immutable-actions
|
|
7
|
+
- sha-pinning
|
|
8
|
+
- security-policy
|
|
9
|
+
- organization-policy
|
|
10
|
+
- enterprise
|
|
11
|
+
- supply-chain
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'not pinned to an immutable SHA'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'Action.*is not pinned.*immutable'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'immutable actions.*required'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
- regex: 'Update the workflow to use the immutable reference'
|
|
20
|
+
flags: 'i'
|
|
21
|
+
error_messages:
|
|
22
|
+
- "Error: Action 'actions/checkout@v4' is not pinned to an immutable SHA. Update the workflow to use the immutable reference."
|
|
23
|
+
- "Actions must be pinned to an immutable commit SHA. Organization policy requires immutable actions."
|
|
24
|
+
- "Error: This action reference is not immutable. The organization requires all actions to be pinned by SHA."
|
|
25
|
+
root_cause: |
|
|
26
|
+
GitHub introduced an organization-level security policy called "Require immutable
|
|
27
|
+
actions" (available in GitHub Enterprise and GitHub Teams since late 2025). When
|
|
28
|
+
enabled by an organization admin, every `uses:` reference in every workflow within
|
|
29
|
+
that organization must point to a full commit SHA rather than a mutable tag (such
|
|
30
|
+
as `@v4`) or branch name (such as `@main`).
|
|
31
|
+
|
|
32
|
+
Tags and branches are mutable — they can be updated to point to a different commit
|
|
33
|
+
at any time. An attacker who compromises an upstream action repository can move a
|
|
34
|
+
widely used tag to point to malicious code. The SHA immutability policy protects
|
|
35
|
+
against this supply-chain attack vector by ensuring the exact code being run is
|
|
36
|
+
pinned and verifiable.
|
|
37
|
+
|
|
38
|
+
When the policy is active, the Actions runner validates every `uses:` reference
|
|
39
|
+
before execution and refuses to run any workflow containing mutable refs, even if
|
|
40
|
+
the referenced tag currently points to a trusted commit. This commonly surfaces
|
|
41
|
+
after an organization adopts the policy post-hoc, breaking all existing workflows.
|
|
42
|
+
fix: |
|
|
43
|
+
Replace every `uses:` tag or branch reference with the full 40-character commit SHA.
|
|
44
|
+
Add the human-readable tag as an inline comment for maintainability.
|
|
45
|
+
|
|
46
|
+
To find the SHA for a specific action version:
|
|
47
|
+
- Visit the action's GitHub releases page (e.g., github.com/actions/checkout/releases/tag/v4)
|
|
48
|
+
- Click the commit SHA link next to the tag
|
|
49
|
+
|
|
50
|
+
Tools that automate SHA pinning across all workflow files:
|
|
51
|
+
- `ratchet` (github.com/thepwagner/ratchet): `ratchet pin .github/workflows/`
|
|
52
|
+
- `pin-github-action` (Mend): available as a GitHub Action or CLI
|
|
53
|
+
- Dependabot with `package-ecosystem: github-actions` maintains pinned SHAs automatically
|
|
54
|
+
fix_code:
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: 'Pin action refs to full commit SHA (with readable comment)'
|
|
57
|
+
code: |
|
|
58
|
+
# BEFORE (fails with immutable actions org policy)
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v4
|
|
61
|
+
- uses: actions/setup-node@v4
|
|
62
|
+
- uses: actions/upload-artifact@v4
|
|
63
|
+
|
|
64
|
+
# AFTER (SHA-pinned with human-readable tag comment)
|
|
65
|
+
steps:
|
|
66
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
67
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
68
|
+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
69
|
+
- language: yaml
|
|
70
|
+
label: 'Dependabot config to keep SHA pins up to date automatically'
|
|
71
|
+
code: |
|
|
72
|
+
# .github/dependabot.yml
|
|
73
|
+
version: 2
|
|
74
|
+
updates:
|
|
75
|
+
- package-ecosystem: github-actions
|
|
76
|
+
directory: /
|
|
77
|
+
schedule:
|
|
78
|
+
interval: weekly
|
|
79
|
+
prevention:
|
|
80
|
+
- 'Audit all workflow files before enabling the org policy — run ratchet or pin-github-action across the entire .github/workflows/ directory first.'
|
|
81
|
+
- 'Enable the policy in a single pilot repository before rolling it out organization-wide to identify and fix all mutable refs.'
|
|
82
|
+
- 'Add actionlint with SHA-pinning rules to CI so new mutable refs are caught in PRs before reaching main.'
|
|
83
|
+
- 'Configure Dependabot for github-actions to automatically open PRs whenever a pinned SHA has a new upstream release.'
|
|
84
|
+
docs:
|
|
85
|
+
- url: 'https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-third-party-actions'
|
|
86
|
+
label: 'Security hardening for GitHub Actions: using third-party actions'
|
|
87
|
+
- url: 'https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization'
|
|
88
|
+
label: 'Requiring immutable action components (org policy settings)'
|
|
89
|
+
- url: 'https://github.com/thepwagner/ratchet'
|
|
90
|
+
label: 'ratchet — CLI tool to pin GitHub Actions refs to SHA'
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
id: runner-environment-156
|
|
2
|
+
title: "ubuntu runner: snap packages unavailable — snapd not running"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- ubuntu
|
|
7
|
+
- snap
|
|
8
|
+
- snapd
|
|
9
|
+
- packages
|
|
10
|
+
- ubuntu-24
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'cannot communicate with server.*snapd\.socket'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'snapd is not running'
|
|
15
|
+
flags: i
|
|
16
|
+
- regex: 'snap.*connect.*refused|dial unix.*snapd'
|
|
17
|
+
flags: i
|
|
18
|
+
- regex: 'snap: command not found'
|
|
19
|
+
flags: i
|
|
20
|
+
error_messages:
|
|
21
|
+
- "error: cannot communicate with server: Post \"http://localhost/v2/snaps/kubectl\": dial unix /run/snapd.socket: connect: no such file or directory"
|
|
22
|
+
- "error: cannot communicate with server: Post http://localhost/v2/snaps: dial unix /run/snapd.socket: connect: no such file or directory"
|
|
23
|
+
- "snapd is not running"
|
|
24
|
+
- "snap: command not found"
|
|
25
|
+
root_cause: |
|
|
26
|
+
GitHub-hosted Ubuntu runners do not run the snapd daemon. The snap socket
|
|
27
|
+
`/run/snapd.socket` is never created, so any `snap install` command immediately fails
|
|
28
|
+
when attempting to communicate with the snap store daemon.
|
|
29
|
+
|
|
30
|
+
While the `snap` binary may be present on the runner image, the background snapd service
|
|
31
|
+
is not started because GitHub-hosted runners use a minimal systemd-free environment.
|
|
32
|
+
Starting snapd manually is not practical — it requires systemd or a complex manual
|
|
33
|
+
daemon startup sequence, and snap packages are confined using AppArmor profiles that
|
|
34
|
+
may also be unavailable in the runner environment.
|
|
35
|
+
|
|
36
|
+
This is frequently encountered when workflows try to install tools distributed as snaps:
|
|
37
|
+
kubectl, microk8s, aws-cli, spotify, and many developer utilities.
|
|
38
|
+
fix: |
|
|
39
|
+
Replace `snap install` with an alternative package source for the tool you need:
|
|
40
|
+
|
|
41
|
+
- **kubectl**: Use `curl -LO https://dl.k8s.io/release/...` or the `azure/setup-kubectl` action
|
|
42
|
+
- **aws-cli v2**: Use `pip install awscli` or the official AWS install script via apt
|
|
43
|
+
- **Helm**: Use the `azure/setup-helm` action or the official install script
|
|
44
|
+
- **General tools**: Check for apt, pip, npm, or GitHub releases as alternatives
|
|
45
|
+
|
|
46
|
+
If you must install a snap, consider using a self-hosted runner with snapd enabled,
|
|
47
|
+
or a Docker container runner with snapd configured.
|
|
48
|
+
fix_code:
|
|
49
|
+
- language: yaml
|
|
50
|
+
label: "Install kubectl via apt instead of snap"
|
|
51
|
+
code: |
|
|
52
|
+
steps:
|
|
53
|
+
- name: Install kubectl
|
|
54
|
+
run: |
|
|
55
|
+
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key \
|
|
56
|
+
| sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
|
|
57
|
+
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' \
|
|
58
|
+
| sudo tee /etc/apt/sources.list.d/kubernetes.list
|
|
59
|
+
sudo apt-get update
|
|
60
|
+
sudo apt-get install -y kubectl
|
|
61
|
+
- language: yaml
|
|
62
|
+
label: "Use dedicated setup actions instead of snap"
|
|
63
|
+
code: |
|
|
64
|
+
steps:
|
|
65
|
+
# Instead of: snap install kubectl --classic
|
|
66
|
+
- uses: azure/setup-kubectl@v4
|
|
67
|
+
with:
|
|
68
|
+
version: 'v1.30.0'
|
|
69
|
+
|
|
70
|
+
# Instead of: snap install helm --classic
|
|
71
|
+
- uses: azure/setup-helm@v4
|
|
72
|
+
with:
|
|
73
|
+
version: '3.14.0'
|
|
74
|
+
|
|
75
|
+
# Instead of: snap install aws-cli --classic
|
|
76
|
+
- name: Install AWS CLI v2
|
|
77
|
+
run: |
|
|
78
|
+
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
79
|
+
unzip awscliv2.zip
|
|
80
|
+
sudo ./aws/install
|
|
81
|
+
prevention:
|
|
82
|
+
- "Audit your workflow for any `snap install` commands — GitHub-hosted runners cannot run snap packages"
|
|
83
|
+
- "Check if the tool has an official GitHub Action (setup-kubectl, setup-helm, etc.) before writing a manual install step"
|
|
84
|
+
- "For tools not on apt, prefer GitHub Releases binary downloads with `curl` and `chmod +x` over snap"
|
|
85
|
+
- "Self-hosted runners with a full Ubuntu desktop/server installation do support snap if needed"
|
|
86
|
+
docs:
|
|
87
|
+
- url: "https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#software-installed-on-github-hosted-runners"
|
|
88
|
+
label: "GitHub Docs: Software installed on GitHub-hosted runners"
|
|
89
|
+
- url: "https://github.com/actions/runner-images/issues/1769"
|
|
90
|
+
label: "runner-images#1769: snap is not supported"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
id: runner-environment-153
|
|
2
|
+
title: "ubuntu runner systemctl fails — systemd not running as PID 1"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- ubuntu
|
|
7
|
+
- systemd
|
|
8
|
+
- systemctl
|
|
9
|
+
- services
|
|
10
|
+
- daemon
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'System has not been booted with systemd as init system \(PID 1\)\. Can''t operate\.'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'Failed to connect to bus: No such file or directory'
|
|
15
|
+
flags: i
|
|
16
|
+
- regex: 'systemctl: command not found'
|
|
17
|
+
flags: i
|
|
18
|
+
error_messages:
|
|
19
|
+
- "System has not been booted with systemd as init system (PID 1). Can't operate."
|
|
20
|
+
- "Failed to connect to bus: No such file or directory"
|
|
21
|
+
- "Failed to start postgresql.service: Unit not found."
|
|
22
|
+
- "System has not been booted with systemd"
|
|
23
|
+
root_cause: |
|
|
24
|
+
GitHub-hosted Ubuntu runners do not run systemd as the init system (PID 1). The runner
|
|
25
|
+
process starts directly inside a container-like environment where systemd is never initialized.
|
|
26
|
+
Any workflow step that calls `sudo systemctl start <service>`, `systemctl enable <service>`,
|
|
27
|
+
or `systemctl status <service>` will immediately fail because the D-Bus socket and systemd
|
|
28
|
+
unit system are not present.
|
|
29
|
+
|
|
30
|
+
This is a fundamental architectural constraint of GitHub-hosted runners — they are ephemeral
|
|
31
|
+
VMs (not containers) but systemd is disabled to reduce boot time and resource overhead. The
|
|
32
|
+
pattern is extremely common when devs copy-paste service startup commands from their local
|
|
33
|
+
Linux environment or documentation into GitHub Actions run: steps.
|
|
34
|
+
|
|
35
|
+
Common affected services: PostgreSQL, MySQL, Redis, MongoDB, Nginx, Apache, RabbitMQ.
|
|
36
|
+
fix: |
|
|
37
|
+
Replace `systemctl` calls with one of these approaches:
|
|
38
|
+
|
|
39
|
+
1. **Use GitHub Actions `services:` containers** — the recommended approach for databases and
|
|
40
|
+
message queues. GitHub spins up Docker containers alongside your job.
|
|
41
|
+
|
|
42
|
+
2. **Use direct daemon start commands** — start the service binary directly:
|
|
43
|
+
- PostgreSQL: `sudo -u postgres pg_ctl -D /etc/postgresql/*/main start`
|
|
44
|
+
- MySQL: `sudo mysqld_safe &`
|
|
45
|
+
- Redis: `redis-server --daemonize yes`
|
|
46
|
+
|
|
47
|
+
3. **Use `service` SysV command** — on Ubuntu runners, SysV init `service` works even without
|
|
48
|
+
systemd: `sudo service postgresql start`
|
|
49
|
+
|
|
50
|
+
fix_code:
|
|
51
|
+
- language: yaml
|
|
52
|
+
label: "Use services: containers block (recommended for databases)"
|
|
53
|
+
code: |
|
|
54
|
+
jobs:
|
|
55
|
+
test:
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
services:
|
|
58
|
+
postgres:
|
|
59
|
+
image: postgres:16
|
|
60
|
+
env:
|
|
61
|
+
POSTGRES_PASSWORD: postgres
|
|
62
|
+
ports:
|
|
63
|
+
- 5432:5432
|
|
64
|
+
options: >-
|
|
65
|
+
--health-cmd pg_isready
|
|
66
|
+
--health-interval 10s
|
|
67
|
+
--health-timeout 5s
|
|
68
|
+
--health-retries 5
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/checkout@v4
|
|
71
|
+
- name: Run tests
|
|
72
|
+
run: npm test
|
|
73
|
+
env:
|
|
74
|
+
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
|
|
75
|
+
- language: yaml
|
|
76
|
+
label: "Use SysV 'service' command as systemctl fallback"
|
|
77
|
+
code: |
|
|
78
|
+
steps:
|
|
79
|
+
- name: Start PostgreSQL
|
|
80
|
+
run: |
|
|
81
|
+
sudo service postgresql start
|
|
82
|
+
sudo service postgresql status
|
|
83
|
+
- name: Create test database
|
|
84
|
+
run: |
|
|
85
|
+
sudo -u postgres psql -c "CREATE DATABASE testdb;"
|
|
86
|
+
prevention:
|
|
87
|
+
- "Use the `services:` block in your workflow for databases and message queues — it's the GitHub Actions-native solution"
|
|
88
|
+
- "Replace `systemctl start X` with `sudo service X start` for SysV-compatible services"
|
|
89
|
+
- "For custom daemons, start them in the background with `&` and poll for readiness"
|
|
90
|
+
- "Never copy-paste `systemctl` commands from local Linux setup scripts into GitHub Actions without adaptation"
|
|
91
|
+
docs:
|
|
92
|
+
- url: "https://docs.github.com/en/actions/use-cases-and-examples/using-containerized-services/about-service-containers"
|
|
93
|
+
label: "GitHub Docs: About service containers"
|
|
94
|
+
- url: "https://docs.github.com/en/actions/use-cases-and-examples/using-containerized-services/creating-postgresql-service-containers"
|
|
95
|
+
label: "GitHub Docs: Creating PostgreSQL service containers"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
id: silent-failures-080
|
|
2
|
+
title: "github.event.commits always empty array on pull_request events"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- pull-request
|
|
7
|
+
- commits
|
|
8
|
+
- event-payload
|
|
9
|
+
- skip-ci
|
|
10
|
+
- multi-trigger
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'github\.event\.commits\[0\]'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'Cannot read propert\w+ of undefined.*message'
|
|
15
|
+
flags: i
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Error: Cannot read properties of undefined (reading 'message')"
|
|
18
|
+
- "github.event.commits[0] evaluates to null on pull_request events"
|
|
19
|
+
- "contains(github.event.commits[0].message, '[skip ci]') always false on PRs"
|
|
20
|
+
root_cause: |
|
|
21
|
+
The `github.event.commits` array is only populated in `push` event payloads.
|
|
22
|
+
On `pull_request` events, `github.event.commits` is always an empty array `[]`.
|
|
23
|
+
Accessing `github.event.commits[0]` returns `null`, and
|
|
24
|
+
`github.event.commits[0].message` returns an empty string without throwing an
|
|
25
|
+
expression error.
|
|
26
|
+
|
|
27
|
+
This is the most common footgun when implementing "[skip ci]" commit message
|
|
28
|
+
detection: the condition works correctly for `push` events but silently never
|
|
29
|
+
triggers (never skips) on `pull_request` events. The workflow runs for every PR
|
|
30
|
+
push regardless of the commit message.
|
|
31
|
+
|
|
32
|
+
The issue affects any multi-trigger workflow with both `push:` and `pull_request:`
|
|
33
|
+
that reads commit data from the event payload.
|
|
34
|
+
fix: |
|
|
35
|
+
Guard `github.event.commits` access by event type, or use PR-specific context:
|
|
36
|
+
1. Use `if: github.event_name == 'push'` before accessing commits.
|
|
37
|
+
2. For PR events, check `github.event.pull_request.title` or `github.event.pull_request.body`
|
|
38
|
+
for skip conditions.
|
|
39
|
+
3. Use the `actions/github-script` action to query commits via the REST API on PR events.
|
|
40
|
+
4. Consider separate workflows for push and pull_request to avoid cross-event payload confusion.
|
|
41
|
+
fix_code:
|
|
42
|
+
- language: yaml
|
|
43
|
+
label: "Wrong: commit message check silently fails on pull_request events"
|
|
44
|
+
code: |
|
|
45
|
+
on: [push, pull_request]
|
|
46
|
+
|
|
47
|
+
jobs:
|
|
48
|
+
build:
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
# github.event.commits[0].message is null on pull_request — condition always false
|
|
51
|
+
if: "!contains(github.event.commits[0].message, '[skip ci]')"
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/checkout@v4
|
|
54
|
+
- run: npm test
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: "Fix: guard by event type and use PR-appropriate skip signal"
|
|
57
|
+
code: |
|
|
58
|
+
on: [push, pull_request]
|
|
59
|
+
|
|
60
|
+
jobs:
|
|
61
|
+
build:
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v4
|
|
65
|
+
|
|
66
|
+
- name: Check skip condition
|
|
67
|
+
id: skip
|
|
68
|
+
run: |
|
|
69
|
+
SKIP=false
|
|
70
|
+
if [[ "${{ github.event_name }}" == "push" ]]; then
|
|
71
|
+
# Commits array is available on push events
|
|
72
|
+
if echo "${{ github.event.commits[0].message }}" | grep -q '\[skip ci\]'; then
|
|
73
|
+
SKIP=true
|
|
74
|
+
fi
|
|
75
|
+
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
|
76
|
+
# Check PR title or body for skip signal on PR events
|
|
77
|
+
if echo "${{ github.event.pull_request.title }}" | grep -q '\[skip ci\]'; then
|
|
78
|
+
SKIP=true
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
echo "skip=$SKIP" >> "$GITHUB_OUTPUT"
|
|
82
|
+
|
|
83
|
+
- name: Run tests
|
|
84
|
+
if: steps.skip.outputs.skip != 'true'
|
|
85
|
+
run: npm test
|
|
86
|
+
prevention:
|
|
87
|
+
- "Never access github.event.commits on workflows with pull_request triggers — it will always be empty."
|
|
88
|
+
- "Use separate workflows for push and pull_request if commit-message-based conditionals are required."
|
|
89
|
+
- "For PR skip conditions, use the PR title or body rather than commit messages."
|
|
90
|
+
- "Check which fields are populated for each event type in the GitHub webhook event payload documentation."
|
|
91
|
+
docs:
|
|
92
|
+
- url: "https://docs.github.com/en/webhooks/webhook-events-and-payloads#push"
|
|
93
|
+
label: "Push webhook event payload (commits array)"
|
|
94
|
+
- url: "https://docs.github.com/en/webhooks/webhook-events-and-payloads#pull_request"
|
|
95
|
+
label: "Pull request webhook event payload"
|
|
96
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-conditions-to-control-job-execution"
|
|
97
|
+
label: "Using conditions to control job execution"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
id: silent-failures-081
|
|
2
|
+
title: '`needs.<job_id>.conclusion` does not exist — correct property is `result`; evaluates to empty string silently'
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- needs-context
|
|
7
|
+
- job-result
|
|
8
|
+
- downstream-jobs
|
|
9
|
+
- if-condition
|
|
10
|
+
- empty-string
|
|
11
|
+
- notification-job
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'needs\.\w+\.conclusion'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "needs.<job_id>.conclusion evaluates to empty string — condition using .conclusion never fires"
|
|
17
|
+
root_cause: |
|
|
18
|
+
The `needs` context exposes `.result` for each upstream job — NOT `.conclusion`.
|
|
19
|
+
Valid values for `needs.<job_id>.result` are: `success`, `failure`, `cancelled`,
|
|
20
|
+
and `skipped`.
|
|
21
|
+
|
|
22
|
+
The property `.conclusion` does not exist on the needs context object. Accessing
|
|
23
|
+
`needs.<job_id>.conclusion` silently evaluates to an empty string `""` with no
|
|
24
|
+
warning, error, or annotation in the workflow logs. The downstream job runs but the
|
|
25
|
+
`if:` condition never matches because `"" == 'failure'` is always false.
|
|
26
|
+
|
|
27
|
+
This confusion arises because the `steps` context exposes BOTH `.conclusion` and
|
|
28
|
+
`.outcome` for step results, leading developers to assume the needs context follows
|
|
29
|
+
the same property naming. The mismatch causes notification jobs, cleanup steps, and
|
|
30
|
+
failure-handler jobs to silently skip execution with no observable error.
|
|
31
|
+
fix: |
|
|
32
|
+
Replace `.conclusion` with `.result` in all `needs` context expressions.
|
|
33
|
+
|
|
34
|
+
WRONG: if: needs.build.conclusion == 'failure' # always empty string, never matches
|
|
35
|
+
RIGHT: if: needs.build.result == 'failure' # correct
|
|
36
|
+
|
|
37
|
+
For aggregate checks, prefer the built-in status functions over manual needs.result
|
|
38
|
+
comparisons — `failure()`, `success()`, `cancelled()`, `always()` — which evaluate
|
|
39
|
+
the combined result of all upstream jobs automatically.
|
|
40
|
+
fix_code:
|
|
41
|
+
- language: yaml
|
|
42
|
+
label: 'Correct: use needs.<job>.result not needs.<job>.conclusion'
|
|
43
|
+
code: |
|
|
44
|
+
jobs:
|
|
45
|
+
build:
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
- run: npm ci && npm test
|
|
50
|
+
|
|
51
|
+
notify-on-failure:
|
|
52
|
+
needs: [build]
|
|
53
|
+
# Use built-in failure() or explicit needs.<job>.result
|
|
54
|
+
if: failure()
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
steps:
|
|
57
|
+
- name: Send failure notification
|
|
58
|
+
run: |
|
|
59
|
+
echo "Build result: ${{ needs.build.result }}"
|
|
60
|
+
# .result values: success | failure | cancelled | skipped
|
|
61
|
+
- language: yaml
|
|
62
|
+
label: 'Multi-job downstream check using needs.result'
|
|
63
|
+
code: |
|
|
64
|
+
jobs:
|
|
65
|
+
deploy:
|
|
66
|
+
needs: [lint, test, build]
|
|
67
|
+
# Explicit per-job result check — use .result not .conclusion
|
|
68
|
+
if: >-
|
|
69
|
+
needs.lint.result == 'success' &&
|
|
70
|
+
needs.test.result == 'success' &&
|
|
71
|
+
needs.build.result == 'success'
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- run: echo "All upstream jobs succeeded"
|
|
75
|
+
prevention:
|
|
76
|
+
- 'The needs context exposes `.result` only — `.conclusion` does not exist. Use `needs.<job>.result` for all downstream conditional checks.'
|
|
77
|
+
- 'Prefer the status check functions `failure()`, `success()`, `cancelled()`, `always()` in downstream job `if:` conditions — they aggregate all upstream job results automatically.'
|
|
78
|
+
- 'Add actionlint to CI — it reports references to undefined context properties including `needs.<job>.conclusion`, catching this mistake at review time.'
|
|
79
|
+
- 'When a notification or cleanup job silently skips, verify the `if:` condition uses `.result` and check the actual result value in the Actions run summary.'
|
|
80
|
+
docs:
|
|
81
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#needs-context'
|
|
82
|
+
label: 'GitHub Actions: needs context — result property'
|
|
83
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-conditions-to-control-job-execution'
|
|
84
|
+
label: 'Using conditions to control job execution'
|
|
85
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#status-check-functions'
|
|
86
|
+
label: 'Status check functions: failure(), success(), cancelled(), always()'
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
id: triggers-059
|
|
2
|
+
title: "workflow_call required: true inputs silently receive empty string when omitted by caller"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- reusable-workflow
|
|
7
|
+
- workflow-call
|
|
8
|
+
- inputs
|
|
9
|
+
- required
|
|
10
|
+
- validation
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'required.*input.*not provided'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'Input .+ is required but was not provided'
|
|
15
|
+
flags: i
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Input 'environment' is required but was not provided"
|
|
18
|
+
- "required: true input silently receives empty string when omitted from caller with:"
|
|
19
|
+
root_cause: |
|
|
20
|
+
When a reusable workflow declares `on.workflow_call.inputs` with `required: true`,
|
|
21
|
+
GitHub does NOT enforce this requirement at call time via the REST API or from other
|
|
22
|
+
calling workflows. The `required: true` flag is informational documentation only —
|
|
23
|
+
it is not validated before the workflow runs.
|
|
24
|
+
|
|
25
|
+
When a calling workflow omits a required input from its `with:` block, the reusable
|
|
26
|
+
workflow receives an empty string `""` for the missing input with no error or warning.
|
|
27
|
+
The workflow starts and runs to whatever conclusion the empty input causes.
|
|
28
|
+
|
|
29
|
+
This leads to silent failures such as deploying to an undefined environment, running
|
|
30
|
+
against an empty target URL, or passing `""` to shell commands that produce incorrect
|
|
31
|
+
results. The bug is especially hard to find because the calling workflow shows a green
|
|
32
|
+
"success" until the silent empty input causes a downstream failure.
|
|
33
|
+
fix: |
|
|
34
|
+
Explicitly validate all required inputs at the start of the reusable workflow:
|
|
35
|
+
1. Add an input-validation step as the FIRST step of the FIRST job that checks each
|
|
36
|
+
required input for emptiness and fails with a descriptive error.
|
|
37
|
+
2. Use `default:` values in `on.workflow_call.inputs` to handle omitted inputs
|
|
38
|
+
gracefully with a fallback rather than silently using empty string.
|
|
39
|
+
3. In calling workflows, always pass all inputs explicitly with `with:` — never
|
|
40
|
+
rely on `required: true` to catch missing inputs.
|
|
41
|
+
fix_code:
|
|
42
|
+
- language: yaml
|
|
43
|
+
label: "Reusable workflow: validate required inputs at startup"
|
|
44
|
+
code: |
|
|
45
|
+
# .github/workflows/deploy.yml (reusable workflow)
|
|
46
|
+
on:
|
|
47
|
+
workflow_call:
|
|
48
|
+
inputs:
|
|
49
|
+
environment:
|
|
50
|
+
required: true # documentation only — NOT enforced by GitHub
|
|
51
|
+
type: string
|
|
52
|
+
target-url:
|
|
53
|
+
required: true
|
|
54
|
+
type: string
|
|
55
|
+
|
|
56
|
+
jobs:
|
|
57
|
+
validate-inputs:
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
steps:
|
|
60
|
+
- name: Validate required inputs
|
|
61
|
+
run: |
|
|
62
|
+
ERRORS=()
|
|
63
|
+
if [[ -z "${{ inputs.environment }}" ]]; then
|
|
64
|
+
ERRORS+=("Required input 'environment' was not provided")
|
|
65
|
+
fi
|
|
66
|
+
if [[ -z "${{ inputs.target-url }}" ]]; then
|
|
67
|
+
ERRORS+=("Required input 'target-url' was not provided")
|
|
68
|
+
fi
|
|
69
|
+
if [[ ${#ERRORS[@]} -gt 0 ]]; then
|
|
70
|
+
for ERR in "${ERRORS[@]}"; do
|
|
71
|
+
echo "::error::$ERR"
|
|
72
|
+
done
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
deploy:
|
|
77
|
+
needs: validate-inputs
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
steps:
|
|
80
|
+
- run: echo "Deploying to ${{ inputs.environment }}"
|
|
81
|
+
- language: yaml
|
|
82
|
+
label: "Fix: use default: values to avoid silent empty string behavior"
|
|
83
|
+
code: |
|
|
84
|
+
on:
|
|
85
|
+
workflow_call:
|
|
86
|
+
inputs:
|
|
87
|
+
environment:
|
|
88
|
+
required: false
|
|
89
|
+
default: 'staging' # explicit default instead of relying on required: true
|
|
90
|
+
type: string
|
|
91
|
+
|
|
92
|
+
jobs:
|
|
93
|
+
deploy:
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
steps:
|
|
96
|
+
- run: echo "Deploying to ${{ inputs.environment }}" # always 'staging' if omitted
|
|
97
|
+
prevention:
|
|
98
|
+
- "Treat required: true on workflow_call inputs as documentation only — always validate inside the reusable workflow."
|
|
99
|
+
- "Add a dedicated input-validation job as the first job in every reusable workflow with required inputs."
|
|
100
|
+
- "Prefer default: values over required: true when a sensible fallback exists."
|
|
101
|
+
- "In calling workflows, always explicitly pass all inputs in the with: block."
|
|
102
|
+
docs:
|
|
103
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onworkflow_callinputs"
|
|
104
|
+
label: "on.workflow_call.inputs syntax reference"
|
|
105
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows#using-inputs-and-secrets-in-a-called-workflow"
|
|
106
|
+
label: "Using inputs in a called workflow"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
id: yaml-syntax-054
|
|
2
|
+
title: "env context silently empty in reusable workflow with: inputs"
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- reusable-workflow
|
|
7
|
+
- env-context
|
|
8
|
+
- with-inputs
|
|
9
|
+
- workflow-call
|
|
10
|
+
- context-availability
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: '"env" context is not available in this context'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'env context.*not available.*reusable'
|
|
15
|
+
flags: i
|
|
16
|
+
error_messages:
|
|
17
|
+
- '"env" context is not available in this context'
|
|
18
|
+
- "Input evaluates to empty string when env context used in reusable workflow with:"
|
|
19
|
+
root_cause: |
|
|
20
|
+
The `env` context is NOT available inside `jobs.<job_id>.with:` blocks used to
|
|
21
|
+
call reusable workflows. Unlike `jobs.<job_id>.steps.with:` (regular action steps,
|
|
22
|
+
which support `env` context), reusable workflow `with:` inputs are evaluated before
|
|
23
|
+
job-level environment variables are injected.
|
|
24
|
+
|
|
25
|
+
Using `${{ env.MY_VAR }}` in a reusable workflow `with:` block silently evaluates
|
|
26
|
+
to an empty string. No error is thrown and the reusable workflow receives `""` for
|
|
27
|
+
that input. The actionlint static linter reports:
|
|
28
|
+
`"env" context is not available in this context`.
|
|
29
|
+
|
|
30
|
+
This is explicitly documented in GitHub's context availability table — `env` is
|
|
31
|
+
available in `jobs.<job_id>.steps.with` but NOT in `jobs.<job_id>.with`.
|
|
32
|
+
fix: |
|
|
33
|
+
Replace `env` context references in reusable workflow `with:` blocks with one of:
|
|
34
|
+
1. `vars` context — for repository or organization-level configuration variables
|
|
35
|
+
(Settings → Variables). Available in reusable workflow `with:`.
|
|
36
|
+
2. Inline literal values directly in `with:`.
|
|
37
|
+
3. Workflow-level `inputs:` context if the calling workflow is itself a reusable
|
|
38
|
+
workflow (chain input passing).
|
|
39
|
+
4. A prior job that captures the value and passes it via `needs.<job>.outputs`.
|
|
40
|
+
fix_code:
|
|
41
|
+
- language: yaml
|
|
42
|
+
label: "Wrong: env context in reusable workflow with: (silently evaluates to empty string)"
|
|
43
|
+
code: |
|
|
44
|
+
env:
|
|
45
|
+
BASE_URL: 'https://api.example.com'
|
|
46
|
+
DEPLOY_ENV: 'production'
|
|
47
|
+
|
|
48
|
+
jobs:
|
|
49
|
+
call-deploy:
|
|
50
|
+
uses: ./.github/workflows/deploy.yml
|
|
51
|
+
with:
|
|
52
|
+
api-url: ${{ env.BASE_URL }} # silently empty — env unavailable in reusable with:
|
|
53
|
+
environment: ${{ env.DEPLOY_ENV }} # also silently empty
|
|
54
|
+
- language: yaml
|
|
55
|
+
label: "Fix: use vars context or inline value"
|
|
56
|
+
code: |
|
|
57
|
+
# Set BASE_URL as a repository variable in Settings → Secrets and variables → Variables
|
|
58
|
+
jobs:
|
|
59
|
+
call-deploy:
|
|
60
|
+
uses: ./.github/workflows/deploy.yml
|
|
61
|
+
with:
|
|
62
|
+
api-url: ${{ vars.BASE_URL }} # vars IS available in reusable with:
|
|
63
|
+
environment: 'production' # inline literal also works
|
|
64
|
+
- language: yaml
|
|
65
|
+
label: "Fix: pass via prior job output if value is dynamic"
|
|
66
|
+
code: |
|
|
67
|
+
jobs:
|
|
68
|
+
resolve-config:
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
outputs:
|
|
71
|
+
api-url: ${{ steps.config.outputs.url }}
|
|
72
|
+
steps:
|
|
73
|
+
- id: config
|
|
74
|
+
run: echo "url=$BASE_URL" >> "$GITHUB_OUTPUT"
|
|
75
|
+
env:
|
|
76
|
+
BASE_URL: ${{ env.BASE_URL }} # env IS available in step env:
|
|
77
|
+
|
|
78
|
+
call-deploy:
|
|
79
|
+
needs: resolve-config
|
|
80
|
+
uses: ./.github/workflows/deploy.yml
|
|
81
|
+
with:
|
|
82
|
+
api-url: ${{ needs.resolve-config.outputs.api-url }} # needs context available
|
|
83
|
+
prevention:
|
|
84
|
+
- "Consult the GitHub Actions context availability table before using contexts in reusable workflow with: blocks."
|
|
85
|
+
- "Prefer vars context (repository/org variables) over env for values shared across workflows."
|
|
86
|
+
- "Run actionlint in CI — it detects unavailable context usage and catches this before runtime."
|
|
87
|
+
- "If a reusable workflow input is silently empty, check context availability as the first debugging step."
|
|
88
|
+
docs:
|
|
89
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#context-availability"
|
|
90
|
+
label: "GitHub Actions context availability table"
|
|
91
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows#passing-inputs-and-secrets-to-a-called-workflow"
|
|
92
|
+
label: "Passing inputs and secrets to a called workflow"
|
|
93
|
+
- url: "https://rhysd.github.io/actionlint/"
|
|
94
|
+
label: "actionlint — static checker for GitHub Actions"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
id: yaml-syntax-055
|
|
2
|
+
title: '`on.workflow_call.outputs.value` references `steps.<id>.outputs` — steps context unavailable at workflow scope; must use `jobs.<job_id>.outputs`'
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- reusable-workflow
|
|
7
|
+
- workflow_call
|
|
8
|
+
- outputs
|
|
9
|
+
- steps-context
|
|
10
|
+
- jobs-context
|
|
11
|
+
- empty-string
|
|
12
|
+
- scope
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'steps\.\w+\.outputs\.\w+'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Reusable workflow output is empty — steps context not accessible in on.workflow_call.outputs.value"
|
|
18
|
+
- "on.workflow_call.outputs value references steps context which is not available at workflow scope"
|
|
19
|
+
root_cause: |
|
|
20
|
+
In a reusable workflow, `on.workflow_call.outputs[*].value:` expressions are
|
|
21
|
+
evaluated at the WORKFLOW level, not within a job. The `steps` context is scoped to
|
|
22
|
+
a specific job and is only accessible inside that job's step expressions — it is
|
|
23
|
+
not visible at the top-level workflow scope where `on.workflow_call.outputs` lives.
|
|
24
|
+
|
|
25
|
+
Using `${{ steps.upload.outputs.artifact-id }}` in the `value:` field silently
|
|
26
|
+
evaluates to an empty string. No error, warning, or annotation is raised; the
|
|
27
|
+
caller simply receives an empty output value.
|
|
28
|
+
|
|
29
|
+
The correct pattern requires two explicit hops:
|
|
30
|
+
1. Capture the step output at the JOB level using the job's `outputs:` block.
|
|
31
|
+
2. Reference the JOB output at the WORKFLOW level using `${{ jobs.<job_id>.outputs.<key> }}`.
|
|
32
|
+
|
|
33
|
+
Note: this is distinct from yaml-syntax-032 (jobs.<id>.result empty in workflow_call
|
|
34
|
+
outputs) which covers the result property specifically, not step output propagation.
|
|
35
|
+
fix: |
|
|
36
|
+
Bridge step outputs up through the job's `outputs:` block first, then reference the
|
|
37
|
+
job output in `on.workflow_call.outputs.value`.
|
|
38
|
+
|
|
39
|
+
Pattern:
|
|
40
|
+
step writes to GITHUB_OUTPUT (or action output)
|
|
41
|
+
→ job outputs: exposes it as jobs.<job_id>.outputs.<key>
|
|
42
|
+
→ on.workflow_call.outputs.value references jobs.<job_id>.outputs.<key>
|
|
43
|
+
fix_code:
|
|
44
|
+
- language: yaml
|
|
45
|
+
label: 'Correct two-hop output propagation for reusable workflows'
|
|
46
|
+
code: |
|
|
47
|
+
on:
|
|
48
|
+
workflow_call:
|
|
49
|
+
outputs:
|
|
50
|
+
artifact-id:
|
|
51
|
+
description: 'ID of the uploaded build artifact'
|
|
52
|
+
# CORRECT: reference job-level output
|
|
53
|
+
value: ${{ jobs.build.outputs.artifact-id }}
|
|
54
|
+
# WRONG (silent empty string):
|
|
55
|
+
# value: ${{ steps.upload.outputs.artifact-id }}
|
|
56
|
+
|
|
57
|
+
jobs:
|
|
58
|
+
build:
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
outputs:
|
|
61
|
+
# Hop 1: expose step output at job level
|
|
62
|
+
artifact-id: ${{ steps.upload.outputs.artifact-id }}
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v4
|
|
65
|
+
|
|
66
|
+
- name: Build
|
|
67
|
+
run: npm ci && npm run build
|
|
68
|
+
|
|
69
|
+
- name: Upload artifact
|
|
70
|
+
id: upload
|
|
71
|
+
uses: actions/upload-artifact@v4
|
|
72
|
+
with:
|
|
73
|
+
name: build-output
|
|
74
|
+
path: dist/
|
|
75
|
+
# Hop 2: job outputs block above captures steps.upload.outputs.artifact-id
|
|
76
|
+
# Hop 3: on.workflow_call.outputs.value references jobs.build.outputs.artifact-id
|
|
77
|
+
prevention:
|
|
78
|
+
- 'The `steps` context is job-scoped. Always bridge step outputs through the job `outputs:` block before referencing them at workflow level in `on.workflow_call.outputs.value:`.'
|
|
79
|
+
- 'Use actionlint to statically analyze reusable workflow output expressions — it reports invalid context usage at workflow scope.'
|
|
80
|
+
- 'When a reusable workflow output is unexpectedly empty in the caller, check whether the `value:` expression uses `steps.` (wrong scope) vs `jobs.` (correct scope).'
|
|
81
|
+
- 'Each output needs both a job-level `outputs:` mapping AND a workflow-level `on.workflow_call.outputs[*].value:` reference — missing either hop silently returns empty string.'
|
|
82
|
+
docs:
|
|
83
|
+
- url: 'https://docs.github.com/en/actions/sharing-automations/reusing-workflows#using-outputs-from-a-reusable-workflow'
|
|
84
|
+
label: 'Reusable workflows: using outputs'
|
|
85
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs'
|
|
86
|
+
label: 'Passing information between jobs (job outputs)'
|
|
87
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#steps-context'
|
|
88
|
+
label: 'Steps context — job-scoped, not available at workflow level'
|
package/package.json
CHANGED