@htekdev/actions-debugger 1.0.22 → 1.0.24

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.
Files changed (61) hide show
  1. package/errors/caching-artifacts/artifact-minimum-retention-one-day.yml +153 -0
  2. package/errors/caching-artifacts/cache-api-propagation-delay-post-save.yml +128 -0
  3. package/errors/caching-artifacts/cache-backend-internal-error-skipped.yml +75 -0
  4. package/errors/caching-artifacts/cache-hit-step-id-case-sensitive-mismatch.yml +95 -0
  5. package/errors/caching-artifacts/cache-save-post-step-skipped-on-failure.yml +114 -0
  6. package/errors/concurrency-timing/deploy-pages-in-progress-deployment-wedged.yml +70 -0
  7. package/errors/concurrency-timing/deployment-review-timeout-expired.yml +88 -0
  8. package/errors/concurrency-timing/job-concurrency-scope-per-run-not-global.yml +81 -0
  9. package/errors/concurrency-timing/merge-queue-concurrency-cancel-blocks-all.yml +86 -0
  10. package/errors/concurrency-timing/reusable-workflow-github-workflow-context-cancel.yml +124 -0
  11. package/errors/concurrency-timing/runner-scale-set-jobs-never-start.yml +123 -0
  12. package/errors/concurrency-timing/runner-temp-dir-race-concurrent-workers.yml +90 -0
  13. package/errors/known-unsolved/artifact-download-url-unauthenticated-404.yml +98 -0
  14. package/errors/known-unsolved/checkout-v6-credentials-docker-run-manual.yml +105 -0
  15. package/errors/known-unsolved/concurrency-groups-repo-scoped-only.yml +138 -0
  16. package/errors/known-unsolved/matrix-256-job-limit.yml +142 -0
  17. package/errors/known-unsolved/merge-group-paths-filter-not-supported.yml +137 -0
  18. package/errors/known-unsolved/no-job-allow-failure.yml +73 -0
  19. package/errors/known-unsolved/reusable-secrets-inherit-not-deep-forwarded.yml +113 -0
  20. package/errors/known-unsolved/schedule-cron-hours-long-queue-drift.yml +101 -0
  21. package/errors/permissions-auth/checkout-persist-credentials-token-write.yml +90 -0
  22. package/errors/permissions-auth/create-github-app-token-cross-job-token-revoked.yml +95 -0
  23. package/errors/permissions-auth/github-token-contents-write-missing-git-push.yml +117 -0
  24. package/errors/permissions-auth/org-actions-policy-blocks-unapproved-action.yml +106 -0
  25. package/errors/runner-environment/codeql-action-v2-deprecated.yml +110 -0
  26. package/errors/runner-environment/macos-26-openssl-3-system-library-breaking.yml +114 -0
  27. package/errors/runner-environment/macos-26-ruby-34-default-upgrade.yml +114 -0
  28. package/errors/runner-environment/macos-26-xcode-default-265-pin-required.yml +99 -0
  29. package/errors/runner-environment/macos-latest-label-switches-to-macos26.yml +127 -0
  30. package/errors/runner-environment/node20-removed-toolcache-default-node22.yml +104 -0
  31. package/errors/runner-environment/org-runner-group-dispatch-null.yml +102 -0
  32. package/errors/runner-environment/powershell-74-76-threadjob-module-rename.yml +124 -0
  33. package/errors/runner-environment/self-hosted-runner-not-found.yml +134 -0
  34. package/errors/runner-environment/self-hosted-runner-selinux-service-exec-failure.yml +116 -0
  35. package/errors/runner-environment/service-container-no-healthcheck.yml +158 -0
  36. package/errors/runner-environment/setup-node-v5-corepack-pnpm-not-found.yml +101 -0
  37. package/errors/runner-environment/setup-node-yarn-not-installed-self-hosted.yml +76 -0
  38. package/errors/runner-environment/setup-python-externally-managed-env-error.yml +95 -0
  39. package/errors/runner-environment/windows-2019-runner-retired-june2025.yml +118 -0
  40. package/errors/runner-environment/windows-2022-docker-daemon-not-started.yml +108 -0
  41. package/errors/silent-failures/cache-hit-output-string-not-boolean.yml +96 -0
  42. package/errors/silent-failures/checkout-lfs-pointer-not-content.yml +105 -0
  43. package/errors/silent-failures/reusable-workflow-output-skipped-contains-secret.yml +115 -0
  44. package/errors/silent-failures/setup-node-silent-download-exit-zero.yml +105 -0
  45. package/errors/silent-failures/setup-python-truncated-manifest-silent-exit.yml +111 -0
  46. package/errors/silent-failures/undefined-env-expression-empty-string-silent.yml +115 -0
  47. package/errors/silent-failures/windows-powershell-github-output-bash-syntax.yml +118 -0
  48. package/errors/triggers/fork-pr-first-time-contributor-approval-required.yml +142 -0
  49. package/errors/triggers/on-push-branches-glob-star-no-slash-match.yml +78 -0
  50. package/errors/triggers/pull-request-target-env-protection-default-branch-eval.yml +117 -0
  51. package/errors/triggers/required-status-check-renamed-never-passes.yml +87 -0
  52. package/errors/triggers/schedule-cron-self-hosted-runner-not-triggered.yml +107 -0
  53. package/errors/triggers/workflow-run-checkout-uses-default-branch.yml +114 -0
  54. package/errors/yaml-syntax/composite-action-run-shell-missing.yml +90 -0
  55. package/errors/yaml-syntax/composite-action-secrets-context-unavailable.yml +99 -0
  56. package/errors/yaml-syntax/github-script-octokit-renamed-to-github.yml +130 -0
  57. package/errors/yaml-syntax/labeler-v5-config-format-breaking.yml +67 -0
  58. package/errors/yaml-syntax/reusable-workflow-nesting-depth-exceeded.yml +113 -0
  59. package/errors/yaml-syntax/runs-on-expression-array-syntax-error.yml +121 -0
  60. package/errors/yaml-syntax/setup-go-matrix-version-float-coercion.yml +69 -0
  61. package/package.json +1 -1
@@ -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"
@@ -0,0 +1,115 @@
1
+ id: silent-failures-032
2
+ title: "Undefined env/context Variable Silently Evaluates to Empty String"
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - env-context
7
+ - expression
8
+ - undefined-variable
9
+ - empty-string
10
+ - silent-failure
11
+ - jenkins-migration
12
+ patterns:
13
+ - regex: '\$\{\{\s*env\.[A-Za-z_][A-Za-z0-9_]*\s*\}\}'
14
+ flags: "i"
15
+ - regex: "expression.*evaluates.*empty.*string|undefined.*variable.*no.*error"
16
+ flags: "i"
17
+ error_messages:
18
+ - "(no error shown -- undefined env/context variables produce empty strings silently)"
19
+ - "(step proceeds with empty value, downstream steps receive blank input)"
20
+ root_cause: |
21
+ GitHub Actions expression evaluation does not throw errors when referencing
22
+ variables that do not exist. An expression like ${{ env.SOME_VAR }} where
23
+ SOME_VAR is not defined in any env: block evaluates silently to an empty string.
24
+
25
+ This behavior affects all expression contexts:
26
+ - ${{ env.VAR_NAME }} -- undefined env variable evaluates to empty string
27
+ - ${{ inputs.param }} -- undefined workflow input evaluates to empty string
28
+ - ${{ vars.CONFIG }} -- undefined repository variable evaluates to empty string
29
+ - ${{ needs.job.outputs.value }} -- missing job output evaluates to empty string
30
+
31
+ Common causes:
32
+ 1. Jenkins migration: GitHub Actions Importer generates env references for all
33
+ Jenkins variables, but not all are defined in the converted workflow env block.
34
+ 2. Variable renaming: renamed in one place but old reference still used elsewhere.
35
+ 3. Conditional env blocks: variable only set for certain trigger events.
36
+ 4. Copy-paste across repos where the env block differs.
37
+
38
+ Silent empty values cause cascading failures:
39
+ - Docker image tags resolve to empty, pushing to wrong registry path silently
40
+ - Build version strings empty, artifacts built without version identifier
41
+ - Path variables empty, steps act on current directory unexpectedly
42
+ - Conditional checks always false because gating variable is always empty
43
+ - Upload artifact path empty, upload succeeds with zero files
44
+ fix: |
45
+ Add explicit variable validation using shell parameter expansion to fail fast
46
+ with a clear error message when required variables are empty or unset.
47
+
48
+ The POSIX ${VAR:?message} syntax causes the step to exit non-zero with a clear
49
+ error immediately when VAR is empty or unset.
50
+
51
+ For non-shell contexts, use the GitHub Actions || operator to provide fallback
52
+ values or error markers.
53
+ fix_code:
54
+ - language: yaml
55
+ label: "Shell validation with POSIX parameter expansion -- fail fast on empty"
56
+ code: |
57
+ jobs:
58
+ deploy:
59
+ env:
60
+ DEPLOY_TARGET: ${{ env.DEPLOY_TARGET }}
61
+ IMAGE_TAG: ${{ env.IMAGE_TAG }}
62
+ steps:
63
+ - name: Validate required environment variables
64
+ shell: bash
65
+ run: |
66
+ : "${DEPLOY_TARGET:?DEPLOY_TARGET is required but not set}"
67
+ : "${IMAGE_TAG:?IMAGE_TAG is required -- check the env: block}"
68
+ echo "All required variables validated"
69
+ - name: Deploy
70
+ run: ./deploy.sh "$DEPLOY_TARGET" "$IMAGE_TAG"
71
+ - language: yaml
72
+ label: "Expression fallback to make undefined variables visible in logs"
73
+ code: |
74
+ steps:
75
+ - name: Resolve configuration
76
+ env:
77
+ DEPLOY_TARGET: ${{ env.DEPLOY_TARGET || 'MISSING_DEPLOY_TARGET' }}
78
+ run: |
79
+ if [[ "$DEPLOY_TARGET" == "MISSING_DEPLOY_TARGET" ]]; then
80
+ echo "::error::DEPLOY_TARGET is not set -- add it to the env: block"
81
+ exit 1
82
+ fi
83
+ echo "Deploying to: $DEPLOY_TARGET"
84
+ - language: yaml
85
+ label: "Bulk audit for Jenkins-migrated workflows"
86
+ code: |
87
+ steps:
88
+ - name: Audit required environment variables
89
+ shell: bash
90
+ run: |
91
+ REQUIRED=(APP_VERSION DEPLOY_ENV BUILD_TARGET REGISTRY_URL)
92
+ MISSING=()
93
+ for VAR in "${REQUIRED[@]}"; do
94
+ [[ -z "${!VAR}" ]] && MISSING+=("$VAR")
95
+ done
96
+ if [[ ${#MISSING[@]} -gt 0 ]]; then
97
+ echo "::error::Missing env vars: ${MISSING[*]} -- add them to env: block"
98
+ exit 1
99
+ fi
100
+ echo "All required variables are set"
101
+ prevention:
102
+ - "After Jenkins migration, audit all env expression references against the workflow env: block."
103
+ - "Add a validation step at job start using shell parameter expansion for all required variables."
104
+ - "Use repository variables (vars.X) instead of env for shared config -- gaps are visible in UI."
105
+ - "Use the || operator to make undefined variables visible: ${{ env.X || 'MISSING' }}."
106
+ - "Set required: true on workflow_dispatch inputs to prevent the undefined-input variant."
107
+ docs:
108
+ - url: "https://stackoverflow.com/questions/76611427/how-to-verify-if-the-github-syntax-variables-actually-exist-in-github-actions-wo"
109
+ label: "Stack Overflow: Verify GitHub syntax variables exist in workflow YAML"
110
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions"
111
+ label: "GitHub Docs: Evaluate expressions -- operators including || for defaults"
112
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables"
113
+ label: "GitHub Docs: Store information in variables -- env, vars, inputs contexts"
114
+ - url: "https://pubs.opengroup.org/onlinepubs/009604499/utilities/xcu_chap02.html#tag_02_06_02"
115
+ label: "POSIX Shell: Parameter expansion with :? for unset/empty detection"