@htekdev/actions-debugger 1.0.23 → 1.0.25
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/artifact-minimum-retention-one-day.yml +153 -0
- package/errors/caching-artifacts/cache-api-propagation-delay-post-save.yml +128 -0
- package/errors/caching-artifacts/cache-backend-internal-error-skipped.yml +75 -0
- package/errors/caching-artifacts/cache-hit-step-id-case-sensitive-mismatch.yml +95 -0
- package/errors/caching-artifacts/cache-save-post-step-skipped-on-failure.yml +114 -0
- package/errors/concurrency-timing/deploy-pages-in-progress-deployment-wedged.yml +70 -0
- package/errors/concurrency-timing/deployment-review-timeout-expired.yml +88 -0
- package/errors/concurrency-timing/job-concurrency-scope-per-run-not-global.yml +81 -0
- package/errors/concurrency-timing/merge-queue-concurrency-cancel-blocks-all.yml +86 -0
- package/errors/concurrency-timing/reusable-workflow-github-workflow-context-cancel.yml +124 -0
- package/errors/concurrency-timing/runner-scale-set-jobs-never-start.yml +123 -0
- package/errors/concurrency-timing/runner-temp-dir-race-concurrent-workers.yml +90 -0
- package/errors/known-unsolved/artifact-download-url-unauthenticated-404.yml +98 -0
- package/errors/known-unsolved/checkout-v6-credentials-docker-run-manual.yml +105 -0
- package/errors/known-unsolved/concurrency-groups-repo-scoped-only.yml +138 -0
- package/errors/known-unsolved/environment-deployment-false-custom-protection.yml +93 -0
- package/errors/known-unsolved/matrix-256-job-limit.yml +142 -0
- package/errors/known-unsolved/merge-group-paths-filter-not-supported.yml +137 -0
- package/errors/known-unsolved/no-job-allow-failure.yml +73 -0
- package/errors/known-unsolved/schedule-cron-hours-long-queue-drift.yml +101 -0
- package/errors/permissions-auth/checkout-persist-credentials-token-write.yml +90 -0
- package/errors/permissions-auth/checkout-v6-cross-repo-token-override.yml +103 -0
- package/errors/permissions-auth/create-github-app-token-cross-job-token-revoked.yml +95 -0
- package/errors/permissions-auth/github-token-contents-write-missing-git-push.yml +117 -0
- package/errors/permissions-auth/org-actions-policy-blocks-unapproved-action.yml +106 -0
- package/errors/runner-environment/codeql-action-v2-deprecated.yml +110 -0
- package/errors/runner-environment/macos-26-openssl-3-system-library-breaking.yml +114 -0
- package/errors/runner-environment/macos-26-ruby-34-default-upgrade.yml +114 -0
- package/errors/runner-environment/macos-26-xcode-default-265-pin-required.yml +99 -0
- package/errors/runner-environment/macos-latest-label-switches-to-macos26.yml +127 -0
- package/errors/runner-environment/maven-gradle-403-cache-backend-outage.yml +116 -0
- package/errors/runner-environment/node20-removed-toolcache-default-node22.yml +104 -0
- package/errors/runner-environment/powershell-74-76-threadjob-module-rename.yml +124 -0
- package/errors/runner-environment/self-hosted-runner-not-found.yml +134 -0
- package/errors/runner-environment/self-hosted-runner-selinux-service-exec-failure.yml +116 -0
- package/errors/runner-environment/service-container-no-healthcheck.yml +158 -0
- package/errors/runner-environment/setup-node-v5-corepack-pnpm-not-found.yml +101 -0
- package/errors/runner-environment/setup-node-yarn-not-installed-self-hosted.yml +76 -0
- package/errors/runner-environment/setup-python-externally-managed-env-error.yml +95 -0
- package/errors/runner-environment/windows-2019-runner-retired-june2025.yml +118 -0
- package/errors/runner-environment/windows-2022-docker-daemon-not-started.yml +108 -0
- package/errors/silent-failures/cache-hit-output-string-not-boolean.yml +96 -0
- package/errors/silent-failures/checkout-lfs-pointer-not-content.yml +105 -0
- package/errors/silent-failures/reusable-workflow-output-skipped-contains-secret.yml +115 -0
- package/errors/silent-failures/setup-node-silent-download-exit-zero.yml +105 -0
- package/errors/silent-failures/setup-python-truncated-manifest-silent-exit.yml +111 -0
- package/errors/silent-failures/undefined-env-expression-empty-string-silent.yml +115 -0
- package/errors/silent-failures/windows-powershell-github-output-bash-syntax.yml +118 -0
- package/errors/triggers/fork-pr-first-time-contributor-approval-required.yml +142 -0
- package/errors/triggers/on-push-branches-glob-star-no-slash-match.yml +78 -0
- package/errors/triggers/pull-request-target-env-protection-default-branch-eval.yml +117 -0
- package/errors/triggers/required-status-check-renamed-never-passes.yml +87 -0
- package/errors/triggers/schedule-cron-self-hosted-runner-not-triggered.yml +107 -0
- package/errors/yaml-syntax/case-function-runner-version-too-old.yml +100 -0
- package/errors/yaml-syntax/composite-action-run-shell-missing.yml +90 -0
- package/errors/yaml-syntax/composite-action-secrets-context-unavailable.yml +99 -0
- package/errors/yaml-syntax/github-script-octokit-renamed-to-github.yml +130 -0
- package/errors/yaml-syntax/labeler-v5-config-format-breaking.yml +67 -0
- package/errors/yaml-syntax/runs-on-expression-array-syntax-error.yml +121 -0
- package/errors/yaml-syntax/setup-go-matrix-version-float-coercion.yml +69 -0
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
id: runner-environment-069
|
|
2
|
+
title: "Windows 2022 Runner Docker Engine Named Pipe Not Found on Start"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- windows
|
|
7
|
+
- docker
|
|
8
|
+
- runner-image
|
|
9
|
+
- intermittent
|
|
10
|
+
- named-pipe
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "failed to connect to the docker API at npipe:////\\.?/pipe/docker_engine"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "open //\\.?/pipe/docker_engine.*The system cannot find the file specified"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "error during connect.*pipe/docker_engine.*daemon running"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "Docker Engine.*Stopped|docker.*service.*not running"
|
|
19
|
+
flags: "i"
|
|
20
|
+
error_messages:
|
|
21
|
+
- "failed to connect to the docker API at npipe:////./pipe/docker_engine; check if the path is correct and if the daemon is running: open //./pipe/docker_engine: The system cannot find the file specified."
|
|
22
|
+
- "error during connect: Get \"http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.45/info\": open //./pipe/docker_engine: The system cannot find the file specified."
|
|
23
|
+
root_cause: |
|
|
24
|
+
On GitHub-hosted `windows-2022` runners, the Docker Engine service occasionally
|
|
25
|
+
fails to start before the workflow job begins. The Docker Engine runs as a Windows
|
|
26
|
+
service (`docker`) and the runner sometimes starts executing job steps before the
|
|
27
|
+
service has fully initialized and opened its named pipe at `//./pipe/docker_engine`.
|
|
28
|
+
|
|
29
|
+
This is an intermittent race condition between the runner agent startup and the
|
|
30
|
+
Docker Engine service startup sequence. The issue was reported in February 2026
|
|
31
|
+
(runner-images#13729) and confirmed to affect `windows-2022` at ~50% frequency
|
|
32
|
+
for some users. The `windows-2025` image is less affected.
|
|
33
|
+
|
|
34
|
+
The Docker Engine service shows `Status: Stopped` when queried immediately after
|
|
35
|
+
the runner starts. Manually starting the service (via `Start-Service docker`)
|
|
36
|
+
resolves the issue for that run. Job reruns also frequently succeed because they
|
|
37
|
+
land on a fresh host with Docker already running.
|
|
38
|
+
fix: |
|
|
39
|
+
Add a step early in your job to verify Docker is running and start it if not:
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
- name: Ensure Docker Engine is running
|
|
43
|
+
shell: pwsh
|
|
44
|
+
run: |
|
|
45
|
+
$service = Get-Service -Name docker -ErrorAction SilentlyContinue
|
|
46
|
+
if ($service.Status -ne 'Running') {
|
|
47
|
+
Start-Service docker
|
|
48
|
+
$timeout = 60
|
|
49
|
+
$elapsed = 0
|
|
50
|
+
while ((Get-Service docker).Status -ne 'Running' -and $elapsed -lt $timeout) {
|
|
51
|
+
Start-Sleep -Seconds 2
|
|
52
|
+
$elapsed += 2
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
docker info
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If the issue is sporadic, a simpler retry on job failure may suffice. You can
|
|
59
|
+
also switch to `windows-2025` which has a lower incidence of this race condition.
|
|
60
|
+
fix_code:
|
|
61
|
+
- language: yaml
|
|
62
|
+
label: "Guard step — ensure Docker service is running before use"
|
|
63
|
+
code: |
|
|
64
|
+
jobs:
|
|
65
|
+
build:
|
|
66
|
+
runs-on: windows-2022
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v4
|
|
69
|
+
|
|
70
|
+
- name: Ensure Docker Engine is running
|
|
71
|
+
shell: pwsh
|
|
72
|
+
run: |
|
|
73
|
+
$svc = Get-Service docker -ErrorAction SilentlyContinue
|
|
74
|
+
if ($null -eq $svc -or $svc.Status -ne 'Running') {
|
|
75
|
+
Write-Host "Docker service not running — starting..."
|
|
76
|
+
Start-Service docker
|
|
77
|
+
$deadline = (Get-Date).AddSeconds(60)
|
|
78
|
+
while ((Get-Service docker).Status -ne 'Running') {
|
|
79
|
+
if ((Get-Date) -gt $deadline) { throw "Docker failed to start in 60s" }
|
|
80
|
+
Start-Sleep -Seconds 2
|
|
81
|
+
}
|
|
82
|
+
Write-Host "Docker service started."
|
|
83
|
+
}
|
|
84
|
+
docker info
|
|
85
|
+
|
|
86
|
+
- name: Build Docker image
|
|
87
|
+
run: docker build -t myimage .
|
|
88
|
+
- language: yaml
|
|
89
|
+
label: "Alternative — switch to windows-2025 (less affected)"
|
|
90
|
+
code: |
|
|
91
|
+
jobs:
|
|
92
|
+
build:
|
|
93
|
+
# windows-2025 has a lower frequency of this race condition
|
|
94
|
+
runs-on: windows-2025
|
|
95
|
+
steps:
|
|
96
|
+
- uses: actions/checkout@v4
|
|
97
|
+
- name: Build Docker image
|
|
98
|
+
run: docker build -t myimage .
|
|
99
|
+
prevention:
|
|
100
|
+
- "Add a Docker health-check step before any `docker` commands on Windows runners."
|
|
101
|
+
- "Consider using `windows-2025` which has fewer reports of this race condition."
|
|
102
|
+
- "Enable job reruns — this race condition is intermittent and reruns usually succeed."
|
|
103
|
+
- "Subscribe to runner-images announcements; GitHub is tracking this as a runner startup issue."
|
|
104
|
+
docs:
|
|
105
|
+
- url: "https://github.com/actions/runner-images/issues/13729"
|
|
106
|
+
label: "GitHub Issue: windows-2022 docker not available when runner starts"
|
|
107
|
+
- url: "https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources"
|
|
108
|
+
label: "About GitHub-hosted runners"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
id: silent-failures-031
|
|
2
|
+
title: "cache-hit Output Is a String Not a Boolean — Bare true Comparison Always False"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- actions/cache
|
|
7
|
+
- cache-hit
|
|
8
|
+
- string-comparison
|
|
9
|
+
- boolean-coercion
|
|
10
|
+
- conditional
|
|
11
|
+
- step-outputs
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "cache-hit\\s*[!=]=\\s*true(?!')"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "if:\\s+steps\\.\\w+\\.outputs\\.cache-hit\\s*$"
|
|
16
|
+
flags: "im"
|
|
17
|
+
error_messages:
|
|
18
|
+
- "steps.cache.outputs.cache-hit == true"
|
|
19
|
+
- "steps.cache.outputs.cache-hit != true"
|
|
20
|
+
root_cause: |
|
|
21
|
+
The `cache-hit` output from `actions/cache` and `actions/cache/restore` is a **string** value
|
|
22
|
+
(`'true'` or `'false'`), not a native boolean. GitHub Actions expression syntax uses strict
|
|
23
|
+
equality for `==` — there is no implicit type coercion between strings and booleans.
|
|
24
|
+
|
|
25
|
+
This means:
|
|
26
|
+
- `steps.cache.outputs.cache-hit == true` → ALWAYS false (string 'true' ≠ boolean true)
|
|
27
|
+
- `steps.cache.outputs.cache-hit != true` → ALWAYS true (install step always runs)
|
|
28
|
+
- `if: steps.cache.outputs.cache-hit` → ALWAYS true ('false' is a non-empty string)
|
|
29
|
+
|
|
30
|
+
The most destructive case: `if: steps.cache.outputs.cache-hit != true` is intended to
|
|
31
|
+
skip the install step on cache hit, but it always evaluates to `true` (runs every time),
|
|
32
|
+
so the install always runs even after a successful cache restore. Build times remain
|
|
33
|
+
unchanged, no error is shown, and the caching appears to be broken.
|
|
34
|
+
|
|
35
|
+
This applies to ALL GitHub Actions step outputs — they are always strings. A separate
|
|
36
|
+
but related issue is `cache-hit-restore-keys-misleading` (cache-hit is 'true' on partial
|
|
37
|
+
key match); this entry covers the unquoted boolean comparison pattern specifically.
|
|
38
|
+
fix: |
|
|
39
|
+
Always compare `cache-hit` to the string `'true'` with single quotes:
|
|
40
|
+
|
|
41
|
+
- Skip install on cache hit: `if: steps.cache.outputs.cache-hit != 'true'`
|
|
42
|
+
- Confirm cache was used: `if: steps.cache.outputs.cache-hit == 'true'`
|
|
43
|
+
|
|
44
|
+
Do NOT use bare `true` / `false` (without quotes) in comparisons with step outputs.
|
|
45
|
+
Do NOT use `if: steps.cache.outputs.cache-hit` as a truthy check — the string 'false'
|
|
46
|
+
is truthy in most contexts and will always pass.
|
|
47
|
+
fix_code:
|
|
48
|
+
- language: yaml
|
|
49
|
+
label: "Correct string comparison for cache-hit (single quotes required)"
|
|
50
|
+
code: |
|
|
51
|
+
- name: Cache node_modules
|
|
52
|
+
id: cache
|
|
53
|
+
uses: actions/cache@v4
|
|
54
|
+
with:
|
|
55
|
+
path: node_modules
|
|
56
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
57
|
+
|
|
58
|
+
# ❌ WRONG: string 'true' != boolean true → always runs (never skips on cache hit)
|
|
59
|
+
- name: Install (broken — always runs)
|
|
60
|
+
if: steps.cache.outputs.cache-hit != true
|
|
61
|
+
run: npm ci
|
|
62
|
+
|
|
63
|
+
# ✅ CORRECT: compare to string 'true' with single quotes
|
|
64
|
+
- name: Install (correct — skips on cache hit)
|
|
65
|
+
if: steps.cache.outputs.cache-hit != 'true'
|
|
66
|
+
run: npm ci
|
|
67
|
+
- language: yaml
|
|
68
|
+
label: "Full cache-then-install pattern with correct comparisons"
|
|
69
|
+
code: |
|
|
70
|
+
- uses: actions/cache@v4
|
|
71
|
+
id: npm-cache
|
|
72
|
+
with:
|
|
73
|
+
path: ~/.npm
|
|
74
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
75
|
+
restore-keys: |
|
|
76
|
+
${{ runner.os }}-npm-
|
|
77
|
+
|
|
78
|
+
- name: Install dependencies
|
|
79
|
+
if: steps.npm-cache.outputs.cache-hit != 'true'
|
|
80
|
+
run: npm ci
|
|
81
|
+
|
|
82
|
+
- name: Confirm cache was used
|
|
83
|
+
if: steps.npm-cache.outputs.cache-hit == 'true'
|
|
84
|
+
run: echo "Cache hit — install skipped"
|
|
85
|
+
prevention:
|
|
86
|
+
- "Always compare step outputs to string literals with quotes: `== 'true'` not `== true`."
|
|
87
|
+
- "Remember: ALL GitHub Actions step outputs are strings — never native booleans or numbers."
|
|
88
|
+
- "Use `actionlint` to lint workflow YAML; it detects boolean vs string type mismatches in conditionals."
|
|
89
|
+
- "Verify caching is working by observing run time reduction — a successful cache hit noticeably speeds up installs."
|
|
90
|
+
docs:
|
|
91
|
+
- url: "https://github.com/actions/cache#outputs"
|
|
92
|
+
label: "actions/cache README: outputs — cache-hit is a string"
|
|
93
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#steps-context"
|
|
94
|
+
label: "GitHub Docs: steps context — all outputs are strings"
|
|
95
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#operators"
|
|
96
|
+
label: "GitHub Docs: Expression operators — == uses strict equality"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
id: silent-failures-033
|
|
2
|
+
title: "actions/checkout lfs: true Leaves LFS Pointer Metadata Instead of Actual File Content"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- checkout
|
|
7
|
+
- git-lfs
|
|
8
|
+
- lfs
|
|
9
|
+
- pointer-file
|
|
10
|
+
- large-file-storage
|
|
11
|
+
- self-hosted
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "version https://git-lfs\\.github\\.com/spec/v1"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "oid\\s+sha256:[0-9a-f]{64}"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "lfs:\\s*true"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "version https://git-lfs.github.com/spec/v1"
|
|
21
|
+
- "oid sha256:f23e4c2b1244bc93085dbccf17c447e54..."
|
|
22
|
+
- "size 58951008"
|
|
23
|
+
root_cause: |
|
|
24
|
+
When actions/checkout runs with lfs: true, it configures LFS credentials and
|
|
25
|
+
attempts to download actual file content for LFS-tracked files. However, the
|
|
26
|
+
step can exit 0 (success) while leaving LFS pointer metadata files on disk
|
|
27
|
+
instead of the actual binary or text content.
|
|
28
|
+
|
|
29
|
+
This happens silently in several situations:
|
|
30
|
+
|
|
31
|
+
- Self-hosted runners without git-lfs installed: the LFS fetch is skipped
|
|
32
|
+
because the binary is not present. No error is emitted.
|
|
33
|
+
- LFS bandwidth quota exhausted: GitHub's LFS bandwidth limit (1 GB/month
|
|
34
|
+
free tier) is silently hit; pointer files remain without a clear warning.
|
|
35
|
+
- Private cross-repo LFS: checking out a different repository with lfs: true
|
|
36
|
+
using a token may fail LFS authentication without surfacing an error.
|
|
37
|
+
- Fork pull requests: LFS objects contributed from fork branches may not be
|
|
38
|
+
accessible to the base repository workflow.
|
|
39
|
+
|
|
40
|
+
The result: downstream tools receive a text file starting with
|
|
41
|
+
"version https://git-lfs.github.com/spec/v1" instead of actual content,
|
|
42
|
+
causing opaque failures in build tools, image processors, or test suites.
|
|
43
|
+
fix: |
|
|
44
|
+
Add an explicit LFS fetch step after checkout. On GitHub-hosted runners,
|
|
45
|
+
lfs: true is generally sufficient if LFS is configured on the repository.
|
|
46
|
+
For self-hosted runners, ensure git-lfs is installed before the checkout
|
|
47
|
+
step runs:
|
|
48
|
+
- Ubuntu/Debian: sudo apt-get install git-lfs
|
|
49
|
+
- macOS: brew install git-lfs
|
|
50
|
+
- Windows: winget install GitHub.GitLFS
|
|
51
|
+
|
|
52
|
+
After installation, run the LFS initialization command (git lfs install)
|
|
53
|
+
once per runner to configure the global LFS hooks.
|
|
54
|
+
|
|
55
|
+
To detect unfetched pointer files, add a validation step that checks for
|
|
56
|
+
the LFS pointer header string in files that should contain real content.
|
|
57
|
+
fix_code:
|
|
58
|
+
- language: yaml
|
|
59
|
+
label: "Self-hosted runner — install git-lfs before checkout"
|
|
60
|
+
code: |
|
|
61
|
+
steps:
|
|
62
|
+
- name: Ensure git-lfs is installed
|
|
63
|
+
run: |
|
|
64
|
+
sudo apt-get update -qq
|
|
65
|
+
sudo apt-get install -y git-lfs
|
|
66
|
+
shell: bash
|
|
67
|
+
|
|
68
|
+
- uses: actions/checkout@v4
|
|
69
|
+
with:
|
|
70
|
+
lfs: true
|
|
71
|
+
|
|
72
|
+
- name: Verify LFS content downloaded
|
|
73
|
+
run: |
|
|
74
|
+
if grep -rl "version https://git-lfs.github.com/spec/v1" . \
|
|
75
|
+
--include="*.bin" --include="*.png" --include="*.zip" 2>/dev/null | head -1 | grep -q .; then
|
|
76
|
+
echo "ERROR: LFS pointer files found — actual content was not downloaded"
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
echo "LFS check passed — no pointer files found"
|
|
80
|
+
shell: bash
|
|
81
|
+
|
|
82
|
+
- language: yaml
|
|
83
|
+
label: "GitHub-hosted runner — explicit lfs: true with verification"
|
|
84
|
+
code: |
|
|
85
|
+
steps:
|
|
86
|
+
- uses: actions/checkout@v4
|
|
87
|
+
with:
|
|
88
|
+
lfs: true
|
|
89
|
+
# lfs: true is usually sufficient on GitHub-hosted runners
|
|
90
|
+
# if LFS quota is not exhausted
|
|
91
|
+
prevention:
|
|
92
|
+
- "Verify git-lfs is installed on all self-hosted runners before running checkout workflows."
|
|
93
|
+
- "Monitor GitHub LFS bandwidth usage in repository Settings > Billing to avoid silent quota exhaustion."
|
|
94
|
+
- "Add a post-checkout verification step that confirms LFS-tracked files contain real content, not pointer metadata."
|
|
95
|
+
- "For fork PRs from external contributors, be aware that LFS objects may not be accessible — consider disabling LFS-dependent tests for fork builds."
|
|
96
|
+
- "Use GitHub-hosted runners for LFS-heavy workflows to avoid manual git-lfs installation and configuration."
|
|
97
|
+
docs:
|
|
98
|
+
- url: "https://stackoverflow.com/questions/61463578/github-actions-actions-checkoutv2-lfs-true-flag-not-converting-pointers-to-actual-files"
|
|
99
|
+
label: "Stack Overflow: actions/checkout lfs:true not converting pointers to actual files (Score: 48, 21K views)"
|
|
100
|
+
- url: "https://github.com/actions/checkout#usage"
|
|
101
|
+
label: "actions/checkout: lfs input documentation"
|
|
102
|
+
- url: "https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-git-large-file-storage"
|
|
103
|
+
label: "GitHub Docs: About Git Large File Storage"
|
|
104
|
+
- url: "https://docs.github.com/en/billing/managing-billing-for-your-products/managing-billing-for-git-large-file-storage/about-billing-for-git-large-file-storage"
|
|
105
|
+
label: "GitHub Docs: About billing for Git LFS"
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
id: silent-failures-030
|
|
2
|
+
title: "Reusable Workflow Output Silently Dropped When Value Contains a Secret Substring"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- reusable-workflows
|
|
7
|
+
- secrets-masking
|
|
8
|
+
- workflow-outputs
|
|
9
|
+
- workflow-call
|
|
10
|
+
- secret-substring
|
|
11
|
+
- output-propagation
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Skip output '\\w+' since it may contain secret"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Skip output.*may contain secret"
|
|
16
|
+
flags: "i"
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Skip output 'file-url' since it may contain secret."
|
|
19
|
+
- "Skip output 'artifact-path' since it may contain secret."
|
|
20
|
+
- "Skip output 'deploy-url' since it may contain secret."
|
|
21
|
+
root_cause: |
|
|
22
|
+
When a reusable workflow (called via `workflow_call`) propagates a workflow-level `output`
|
|
23
|
+
whose value contains a substring matching any registered secret, the runner **silently drops
|
|
24
|
+
the entire output** and logs "Skip output 'X' since it may contain secret." in the callee
|
|
25
|
+
workflow's log. The calling workflow's `needs.<job>.outputs.X` resolves to an empty string
|
|
26
|
+
with no error or warning in the caller's logs.
|
|
27
|
+
|
|
28
|
+
The runner performs substring matching — if ANY registered secret appears anywhere within the
|
|
29
|
+
output string (even as a short substring), the runner refuses to propagate the output. Common
|
|
30
|
+
triggers:
|
|
31
|
+
- S3 URLs where part of the bucket path matches an AWS access key fragment
|
|
32
|
+
- File paths with directory components that happen to match a short registered secret
|
|
33
|
+
- Outputs containing account IDs, port numbers, or other short secrets that appear in
|
|
34
|
+
longer strings by coincidence
|
|
35
|
+
- Base64-encoded outputs whose encoding coincidentally contains a secret substring
|
|
36
|
+
|
|
37
|
+
This is distinct from `job-output-masked-as-secret-empty` (regular job step outputs masked
|
|
38
|
+
by actions/runner#1498). The "Skip output" message only appears in reusable workflow output
|
|
39
|
+
propagation and is ONLY visible in the callee's log — the caller shows no warning, making
|
|
40
|
+
diagnosis extremely difficult.
|
|
41
|
+
fix: |
|
|
42
|
+
Avoid including values that contain (or match substrings of) registered secrets in reusable
|
|
43
|
+
workflow outputs. Structural approaches:
|
|
44
|
+
|
|
45
|
+
1. **Return only the non-secret portion** of a path or URL, and reconstruct the full value
|
|
46
|
+
in the calling workflow using `vars` context or a known prefix.
|
|
47
|
+
2. **Use artifacts** (`actions/upload-artifact` / `actions/download-artifact`) to pass files
|
|
48
|
+
or large data payloads between the callee and caller instead of workflow outputs.
|
|
49
|
+
3. **Check callee logs** for "Skip output" messages — they are invisible from the caller side.
|
|
50
|
+
4. **Review short secrets**: secrets shorter than ~8 characters risk false-positive substring
|
|
51
|
+
matches. Consider rotating short secrets to longer values.
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "Return non-secret path portion; reconstruct in caller"
|
|
55
|
+
code: |
|
|
56
|
+
# ❌ BROKEN: output contains secret substring (S3 bucket name matches a secret)
|
|
57
|
+
on:
|
|
58
|
+
workflow_call:
|
|
59
|
+
outputs:
|
|
60
|
+
artifact-url:
|
|
61
|
+
value: ${{ jobs.build.outputs.url }} # silently skipped if URL contains secret
|
|
62
|
+
jobs:
|
|
63
|
+
build:
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
outputs:
|
|
66
|
+
url: ${{ steps.upload.outputs.file-url }}
|
|
67
|
+
steps:
|
|
68
|
+
- id: upload
|
|
69
|
+
run: |
|
|
70
|
+
echo "file-url=https://s3.amazonaws.com/${{ secrets.BUCKET_NAME }}/build-${{ github.run_id }}.zip" \
|
|
71
|
+
>> "$GITHUB_OUTPUT"
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
# ✅ WORKAROUND: return only the non-secret key; caller prepends known S3 prefix
|
|
76
|
+
on:
|
|
77
|
+
workflow_call:
|
|
78
|
+
outputs:
|
|
79
|
+
artifact-key:
|
|
80
|
+
value: ${{ jobs.build.outputs.key }} # just "build-12345678.zip" — no secret
|
|
81
|
+
jobs:
|
|
82
|
+
build:
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
outputs:
|
|
85
|
+
key: ${{ steps.upload.outputs.key }}
|
|
86
|
+
steps:
|
|
87
|
+
- id: upload
|
|
88
|
+
run: echo "key=build-${{ github.run_id }}.zip" >> "$GITHUB_OUTPUT"
|
|
89
|
+
- language: yaml
|
|
90
|
+
label: "Use artifacts to pass files between reusable workflow and caller"
|
|
91
|
+
code: |
|
|
92
|
+
# In the reusable workflow job:
|
|
93
|
+
- uses: actions/upload-artifact@v4
|
|
94
|
+
with:
|
|
95
|
+
name: build-output-${{ github.run_id }}
|
|
96
|
+
path: dist/
|
|
97
|
+
|
|
98
|
+
# In the calling workflow job (after the reusable workflow completes):
|
|
99
|
+
- uses: actions/download-artifact@v4
|
|
100
|
+
with:
|
|
101
|
+
name: build-output-${{ github.run_id }}
|
|
102
|
+
path: dist/
|
|
103
|
+
prevention:
|
|
104
|
+
- "Never include secret-containing values (URLs, paths, credentials) as reusable workflow outputs."
|
|
105
|
+
- "Reserve workflow outputs for short, non-sensitive metadata: version strings, boolean flags, run IDs."
|
|
106
|
+
- "Use artifacts for any data payload that might contain a value related to a registered secret."
|
|
107
|
+
- "Always check callee workflow logs — not just caller logs — for 'Skip output' messages."
|
|
108
|
+
- "Register secrets that are long and unique enough not to appear as substrings in build output values."
|
|
109
|
+
docs:
|
|
110
|
+
- url: "https://stackoverflow.com/questions/72536256/output-in-reusable-workflow-is-incorrectly-recognized-as-secret-github-actions"
|
|
111
|
+
label: "SO#72536256 — Output incorrectly recognized as secret in reusable workflow (5,709 views)"
|
|
112
|
+
- url: "https://stackoverflow.com/questions/75061897/github-actions-incorrectly-thinks-variable-is-a-secret-and-so-does-not-set-outpu"
|
|
113
|
+
label: "SO#75061897 — Actions incorrectly thinks variable is a secret (2,780 views)"
|
|
114
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows#using-outputs-from-a-reusable-workflow"
|
|
115
|
+
label: "GitHub Docs: Using outputs from a reusable workflow"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
id: silent-failures-028
|
|
2
|
+
title: "setup-node Silently Exits 0 Without Installing Node.js on Self-Hosted Runners"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- setup-node
|
|
7
|
+
- self-hosted
|
|
8
|
+
- download-failure
|
|
9
|
+
- silent-exit
|
|
10
|
+
- node-not-found
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'Attempting to download [0-9]+\.[0-9]+\.[0-9]+\.\.\.'
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "exec: node: not found"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "node: not found"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "Attempting to download 24.15.0..."
|
|
20
|
+
- "exec: node: not found"
|
|
21
|
+
- "node: not found"
|
|
22
|
+
- "/usr/bin/bash: node: not found"
|
|
23
|
+
root_cause: |
|
|
24
|
+
On self-hosted runners (particularly those using `gha-runner-scale-set` or ephemeral
|
|
25
|
+
ARC runners), `actions/setup-node` can sporadically print
|
|
26
|
+
"Attempting to download <version>..." and then silently exit 0 without completing
|
|
27
|
+
the download or install.
|
|
28
|
+
|
|
29
|
+
The step outcome is `success` and the runner logs show no error — the action
|
|
30
|
+
simply returns without installing Node.js. Subsequent steps that invoke `node`,
|
|
31
|
+
`npm`, `pnpm`, or other Node-dependent tools then fail with "exec: node: not found"
|
|
32
|
+
or equivalent errors, which are far from the actual root cause.
|
|
33
|
+
|
|
34
|
+
This is a transient network or HTTP client failure in the `@actions/tool-cache`
|
|
35
|
+
download path. When the initial download attempt encounters a non-fatal HTTP error
|
|
36
|
+
(connection reset, early EOF, or interrupted read), the action sometimes swallows
|
|
37
|
+
the error and exits cleanly rather than retrying or calling `core.setFailed()`.
|
|
38
|
+
The issue is more common on self-hosted runners where network conditions are more
|
|
39
|
+
variable than on GitHub-hosted runners.
|
|
40
|
+
|
|
41
|
+
The action version affected is v6.x on self-hosted runners with `gha-runner-scale-set`
|
|
42
|
+
0.13.x and runner 2.334.x. Retrying the job almost always succeeds.
|
|
43
|
+
fix: |
|
|
44
|
+
Add an explicit Node version verification step after setup-node to catch silent
|
|
45
|
+
failures before they cause confusing downstream errors:
|
|
46
|
+
|
|
47
|
+
- run: node --version
|
|
48
|
+
|
|
49
|
+
If this step fails, the issue is with setup-node — not the downstream tool.
|
|
50
|
+
|
|
51
|
+
For persistent failures, add a retry wrapper around setup-node, or use the
|
|
52
|
+
runner's pre-installed Node.js version as a fallback by pinning node-version
|
|
53
|
+
to the runner's system Node.
|
|
54
|
+
|
|
55
|
+
Long-term: upgrade to the latest setup-node patch release — several network
|
|
56
|
+
resilience improvements have been made to the download path. File an issue at
|
|
57
|
+
actions/setup-node with runner version, scale-set version, and the relevant
|
|
58
|
+
log section.
|
|
59
|
+
fix_code:
|
|
60
|
+
- language: yaml
|
|
61
|
+
label: "Add immediate verification to catch silent download failure"
|
|
62
|
+
code: |
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v4
|
|
65
|
+
- uses: actions/setup-node@v6
|
|
66
|
+
with:
|
|
67
|
+
node-version-file: .nvmrc
|
|
68
|
+
cache: pnpm
|
|
69
|
+
|
|
70
|
+
# Catch silent exit-0 failures immediately
|
|
71
|
+
- name: Verify Node.js installed
|
|
72
|
+
run: |
|
|
73
|
+
node --version || (echo "::error::setup-node silently failed — Node.js not installed" && exit 1)
|
|
74
|
+
npm --version
|
|
75
|
+
|
|
76
|
+
- run: pnpm install --frozen-lockfile
|
|
77
|
+
- language: yaml
|
|
78
|
+
label: "Retry wrapper using nick-invision/retry"
|
|
79
|
+
code: |
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@v4
|
|
82
|
+
- name: Setup Node.js with retry
|
|
83
|
+
uses: nick-invision/retry@v3
|
|
84
|
+
with:
|
|
85
|
+
timeout_minutes: 5
|
|
86
|
+
max_attempts: 3
|
|
87
|
+
command: |
|
|
88
|
+
# Re-run setup-node inline
|
|
89
|
+
echo "Attempt ${{ github.run_attempt }}"
|
|
90
|
+
- uses: actions/setup-node@v6
|
|
91
|
+
with:
|
|
92
|
+
node-version: 24
|
|
93
|
+
prevention:
|
|
94
|
+
- "Always add a node --version step after setup-node on self-hosted runners to catch silent failures."
|
|
95
|
+
- "Use check-latest: true to ensure the action validates the installed version before proceeding."
|
|
96
|
+
- "Report sporadic failures with full runner version, scale-set version, and log output to actions/setup-node."
|
|
97
|
+
- "Consider using the runner's pre-cached Node.js version (omit node-version) to avoid downloads on self-hosted runners."
|
|
98
|
+
- "Monitor for setup-node step outcome === 'success' followed by downstream node-not-found — this pattern indicates silent download failure."
|
|
99
|
+
docs:
|
|
100
|
+
- url: "https://github.com/actions/setup-node/issues/1555"
|
|
101
|
+
label: "actions/setup-node#1555: Sporadic silent failure when downloading Node.js (12 reactions)"
|
|
102
|
+
- url: "https://github.com/actions/download-artifact/issues/454"
|
|
103
|
+
label: "actions/download-artifact#454: Related silent download failure pattern"
|
|
104
|
+
- url: "https://github.com/actions/toolkit/tree/main/packages/tool-cache"
|
|
105
|
+
label: "actions/toolkit: tool-cache package (download internals)"
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
id: silent-failures-029
|
|
2
|
+
title: "setup-python Silently Exits 0 Without Installing Python When versions-manifest.json Is Truncated"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- setup-python
|
|
7
|
+
- versions-manifest
|
|
8
|
+
- silent-exit
|
|
9
|
+
- truncated-response
|
|
10
|
+
- python-not-installed
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "evaluating 0 versions"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "Version .+ was not found in the local cache"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "Node Action run completed with exit code 0"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "evaluating 0 versions"
|
|
20
|
+
- "Version 3.11 - 3.13 was not found in the local cache"
|
|
21
|
+
- "Getting manifest from actions/python-versions@main"
|
|
22
|
+
- "##[debug]Node Action run completed with exit code 0"
|
|
23
|
+
- "##[debug]Finishing: Setup python"
|
|
24
|
+
root_cause: |
|
|
25
|
+
`actions/setup-python` fetches `versions-manifest.json` from the
|
|
26
|
+
`actions/python-versions` repository via two GitHub API calls:
|
|
27
|
+
|
|
28
|
+
1. A git tree request to locate the blob URL.
|
|
29
|
+
2. A raw blob fetch with `Accept: application/vnd.github.VERSION.raw`.
|
|
30
|
+
|
|
31
|
+
Sporadically, the second call returns HTTP 200 with a **truncated body** — the
|
|
32
|
+
JSON is cut off mid-object and unparseable. The action catches the JSON parse
|
|
33
|
+
error silently, treats the result as an empty release list, logs
|
|
34
|
+
"evaluating 0 versions", and exits with code 0 **without installing Python**.
|
|
35
|
+
|
|
36
|
+
The truncated response has two consistent symptoms:
|
|
37
|
+
- A ~30-second server-side delay before any bytes arrive.
|
|
38
|
+
- Only affects authenticated requests (5,000 req/hour tier); anonymous requests
|
|
39
|
+
return the full manifest, but the 60 req/hour limit is too low for real CI.
|
|
40
|
+
|
|
41
|
+
After the step exits 0, subsequent steps that call `python`, `pip`, or `python3`
|
|
42
|
+
either use the system Python (which may differ in version) or fail with
|
|
43
|
+
"python: not found" — neither of which points back to the manifest fetch failure.
|
|
44
|
+
fix: |
|
|
45
|
+
Add an explicit Python version check immediately after setup-python to catch the
|
|
46
|
+
silent exit before it causes confusing downstream errors:
|
|
47
|
+
|
|
48
|
+
- run: python --version
|
|
49
|
+
|
|
50
|
+
For a more robust workaround, set `python-version` to an exact version string
|
|
51
|
+
(e.g., "3.11.9") rather than a range — exact versions can fall back to the
|
|
52
|
+
runner's pre-cached toolcache without requiring a manifest fetch.
|
|
53
|
+
|
|
54
|
+
If you must use a version range, add a shell-level retry loop around the Python
|
|
55
|
+
invocation, or use the `setup-python` `check-latest: false` option with a pinned
|
|
56
|
+
version to reduce manifest fetches.
|
|
57
|
+
|
|
58
|
+
File the issue with captured truncated manifest bodies at actions/setup-python
|
|
59
|
+
to help maintainers add retry-with-backoff logic to the manifest fetch path.
|
|
60
|
+
fix_code:
|
|
61
|
+
- language: yaml
|
|
62
|
+
label: "Catch silent exit immediately after setup-python"
|
|
63
|
+
code: |
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/setup-python@v5
|
|
66
|
+
with:
|
|
67
|
+
python-version: "3.11 - 3.13"
|
|
68
|
+
cache: pip
|
|
69
|
+
|
|
70
|
+
# Catch silent manifest-fetch failure before downstream confusion
|
|
71
|
+
- name: Verify Python installed
|
|
72
|
+
run: |
|
|
73
|
+
python --version || (echo "::error::setup-python silently failed — Python not installed" && exit 1)
|
|
74
|
+
pip --version
|
|
75
|
+
|
|
76
|
+
- run: pip install -r requirements.txt
|
|
77
|
+
- language: yaml
|
|
78
|
+
label: "Use exact version to avoid manifest fetch entirely (uses toolcache)"
|
|
79
|
+
code: |
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/setup-python@v5
|
|
82
|
+
with:
|
|
83
|
+
python-version: "3.11.9" # exact version — uses pre-cached toolcache
|
|
84
|
+
cache: pip # no manifest fetch needed for exact match
|
|
85
|
+
- run: python --version
|
|
86
|
+
- run: pip install -r requirements.txt
|
|
87
|
+
- language: yaml
|
|
88
|
+
label: "Use .python-version file for explicit pinning"
|
|
89
|
+
code: |
|
|
90
|
+
# .python-version file: 3.12.7
|
|
91
|
+
steps:
|
|
92
|
+
- uses: actions/setup-python@v5
|
|
93
|
+
with:
|
|
94
|
+
python-version-file: .python-version # exact pin, no manifest range fetch
|
|
95
|
+
cache: pip
|
|
96
|
+
- run: python --version
|
|
97
|
+
prevention:
|
|
98
|
+
- "Always add a python --version verification step after setup-python to surface silent failures immediately."
|
|
99
|
+
- "Pin to an exact Python version (e.g., '3.11.9') rather than a range to use the pre-cached toolcache and avoid manifest fetches."
|
|
100
|
+
- "Use a .python-version file for reproducible, exact pinning that also works with pyenv locally."
|
|
101
|
+
- "Monitor for the symptom pattern: setup-python exits 0 + 'evaluating 0 versions' + downstream python-not-found."
|
|
102
|
+
- "If using version ranges, run setup-python with cache: false first in a debug context to isolate manifest vs. cache failures."
|
|
103
|
+
docs:
|
|
104
|
+
- url: "https://github.com/actions/setup-python/issues/1318"
|
|
105
|
+
label: "actions/setup-python#1318: setup-python silently exits 0 on truncated versions-manifest.json"
|
|
106
|
+
- url: "https://github.com/actions/python-versions"
|
|
107
|
+
label: "actions/python-versions: Source of versions-manifest.json"
|
|
108
|
+
- url: "https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#specifying-a-python-version"
|
|
109
|
+
label: "GitHub Docs: Specifying a Python version"
|
|
110
|
+
- url: "https://github.com/actions/setup-python/blob/main/docs/advanced-usage.md"
|
|
111
|
+
label: "setup-python: Advanced Usage"
|