@htekdev/actions-debugger 1.0.23 → 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 (57) 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/schedule-cron-hours-long-queue-drift.yml +101 -0
  20. package/errors/permissions-auth/checkout-persist-credentials-token-write.yml +90 -0
  21. package/errors/permissions-auth/create-github-app-token-cross-job-token-revoked.yml +95 -0
  22. package/errors/permissions-auth/github-token-contents-write-missing-git-push.yml +117 -0
  23. package/errors/permissions-auth/org-actions-policy-blocks-unapproved-action.yml +106 -0
  24. package/errors/runner-environment/codeql-action-v2-deprecated.yml +110 -0
  25. package/errors/runner-environment/macos-26-openssl-3-system-library-breaking.yml +114 -0
  26. package/errors/runner-environment/macos-26-ruby-34-default-upgrade.yml +114 -0
  27. package/errors/runner-environment/macos-26-xcode-default-265-pin-required.yml +99 -0
  28. package/errors/runner-environment/macos-latest-label-switches-to-macos26.yml +127 -0
  29. package/errors/runner-environment/node20-removed-toolcache-default-node22.yml +104 -0
  30. package/errors/runner-environment/powershell-74-76-threadjob-module-rename.yml +124 -0
  31. package/errors/runner-environment/self-hosted-runner-not-found.yml +134 -0
  32. package/errors/runner-environment/self-hosted-runner-selinux-service-exec-failure.yml +116 -0
  33. package/errors/runner-environment/service-container-no-healthcheck.yml +158 -0
  34. package/errors/runner-environment/setup-node-v5-corepack-pnpm-not-found.yml +101 -0
  35. package/errors/runner-environment/setup-node-yarn-not-installed-self-hosted.yml +76 -0
  36. package/errors/runner-environment/setup-python-externally-managed-env-error.yml +95 -0
  37. package/errors/runner-environment/windows-2019-runner-retired-june2025.yml +118 -0
  38. package/errors/runner-environment/windows-2022-docker-daemon-not-started.yml +108 -0
  39. package/errors/silent-failures/cache-hit-output-string-not-boolean.yml +96 -0
  40. package/errors/silent-failures/checkout-lfs-pointer-not-content.yml +105 -0
  41. package/errors/silent-failures/reusable-workflow-output-skipped-contains-secret.yml +115 -0
  42. package/errors/silent-failures/setup-node-silent-download-exit-zero.yml +105 -0
  43. package/errors/silent-failures/setup-python-truncated-manifest-silent-exit.yml +111 -0
  44. package/errors/silent-failures/undefined-env-expression-empty-string-silent.yml +115 -0
  45. package/errors/silent-failures/windows-powershell-github-output-bash-syntax.yml +118 -0
  46. package/errors/triggers/fork-pr-first-time-contributor-approval-required.yml +142 -0
  47. package/errors/triggers/on-push-branches-glob-star-no-slash-match.yml +78 -0
  48. package/errors/triggers/pull-request-target-env-protection-default-branch-eval.yml +117 -0
  49. package/errors/triggers/required-status-check-renamed-never-passes.yml +87 -0
  50. package/errors/triggers/schedule-cron-self-hosted-runner-not-triggered.yml +107 -0
  51. package/errors/yaml-syntax/composite-action-run-shell-missing.yml +90 -0
  52. package/errors/yaml-syntax/composite-action-secrets-context-unavailable.yml +99 -0
  53. package/errors/yaml-syntax/github-script-octokit-renamed-to-github.yml +130 -0
  54. package/errors/yaml-syntax/labeler-v5-config-format-breaking.yml +67 -0
  55. package/errors/yaml-syntax/runs-on-expression-array-syntax-error.yml +121 -0
  56. package/errors/yaml-syntax/setup-go-matrix-version-float-coercion.yml +69 -0
  57. package/package.json +1 -1
@@ -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"
@@ -0,0 +1,118 @@
1
+ id: silent-failures-034
2
+ title: "Windows PowerShell GITHUB_OUTPUT and GITHUB_ENV Require $env: Prefix, Not Bash-Style $VARIABLE Syntax"
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - windows
7
+ - powershell
8
+ - GITHUB_OUTPUT
9
+ - GITHUB_ENV
10
+ - environment-variables
11
+ - shell-syntax
12
+ patterns:
13
+ - regex: "echo\\s+[\"']?[A-Z_]+=.*>>\\s+\\$GITHUB_OUTPUT"
14
+ flags: "i"
15
+ - regex: "echo\\s+[\"']?[A-Z_]+=.*>>\\s+\\$GITHUB_ENV"
16
+ flags: "i"
17
+ - regex: "shell:\\s*(pwsh|powershell)"
18
+ flags: "i"
19
+ error_messages:
20
+ - "echo \"KEY=value\" >> $GITHUB_OUTPUT"
21
+ - "echo \"KEY=value\" >> $GITHUB_ENV"
22
+ - "Val1: "
23
+ - "Val2: "
24
+ root_cause: |
25
+ When a workflow step uses shell: pwsh (PowerShell Core, the default on Windows
26
+ runners) or shell: powershell (legacy Windows PowerShell), the bash-style
27
+ variable syntax $GITHUB_OUTPUT and $GITHUB_ENV does NOT work.
28
+
29
+ In PowerShell, $GITHUB_OUTPUT is treated as a PowerShell variable (undefined
30
+ and therefore null). The >> append operator writes nothing to a null path.
31
+ The step exits 0 (success) with no error or warning. All downstream jobs
32
+ that reference step outputs or environment variables receive empty strings.
33
+
34
+ The correct PowerShell syntax uses $env:GITHUB_OUTPUT to access the OS
35
+ environment variable, not $GITHUB_OUTPUT (a PS variable).
36
+
37
+ Shell-specific syntax comparison:
38
+ - bash / sh: echo "KEY=val" >> $GITHUB_OUTPUT
39
+ - pwsh (PS 7+): "KEY=val" >> $env:GITHUB_OUTPUT
40
+ - powershell: "KEY=val" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
41
+ - cmd: echo KEY=val >> %GITHUB_OUTPUT%
42
+
43
+ This is a common mistake when:
44
+ - Migrating workflows from Linux to Windows runners
45
+ - Copying bash examples from documentation into PowerShell steps
46
+ - Writing cross-platform workflows without specifying shell: per step
47
+ fix: |
48
+ Replace $GITHUB_OUTPUT with $env:GITHUB_OUTPUT and $GITHUB_ENV with
49
+ $env:GITHUB_ENV in all PowerShell steps.
50
+
51
+ Alternatively, add shell: bash to every run: step that uses bash-style
52
+ syntax — Git Bash is available on all Windows GitHub-hosted runners and
53
+ supports the standard $GITHUB_OUTPUT syntax.
54
+
55
+ For old Windows PowerShell (shell: powershell), use Out-File with
56
+ explicit UTF-8 encoding to avoid BOM issues.
57
+ fix_code:
58
+ - language: yaml
59
+ label: "Broken — bash syntax in PowerShell step silently produces no output"
60
+ code: |
61
+ - name: Set output (broken on PowerShell)
62
+ shell: pwsh
63
+ run: |
64
+ # WRONG: $GITHUB_OUTPUT is a null PS variable — no output is set
65
+ echo "BUILD_VERSION=1.2.3" >> $GITHUB_OUTPUT
66
+
67
+ - language: yaml
68
+ label: "Fixed — correct $env: prefix for PowerShell Core (pwsh)"
69
+ code: |
70
+ - name: Set output (PowerShell Core)
71
+ id: version
72
+ shell: pwsh
73
+ run: |
74
+ # CORRECT: $env:GITHUB_OUTPUT accesses the OS environment variable
75
+ "BUILD_VERSION=1.2.3" >> $env:GITHUB_OUTPUT
76
+
77
+ - name: Use output
78
+ run: echo "Version is ${{ steps.version.outputs.BUILD_VERSION }}"
79
+ shell: pwsh
80
+
81
+ - language: yaml
82
+ label: "Old Windows PowerShell (shell: powershell) — explicit UTF-8 required"
83
+ code: |
84
+ - name: Set output (legacy Windows PowerShell)
85
+ shell: powershell
86
+ run: |
87
+ "BUILD_VERSION=1.2.3" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
88
+
89
+ - language: yaml
90
+ label: "Cross-platform — use shell: bash to keep bash-style syntax on Windows"
91
+ code: |
92
+ - name: Set output (bash on Windows runner)
93
+ shell: bash
94
+ run: |
95
+ # shell: bash uses Git Bash on Windows — $GITHUB_OUTPUT works fine
96
+ echo "BUILD_VERSION=1.2.3" >> $GITHUB_OUTPUT
97
+
98
+ - language: yaml
99
+ label: "CMD shell syntax"
100
+ code: |
101
+ - name: Set output (CMD)
102
+ shell: cmd
103
+ run: echo BUILD_VERSION=1.2.3 >> %GITHUB_OUTPUT%
104
+ prevention:
105
+ - "Use shell: bash for all run: steps that use bash-style $GITHUB_OUTPUT or $GITHUB_ENV syntax, even on Windows runners."
106
+ - "In team workflow templates, document the shell-specific syntax for GITHUB_OUTPUT and GITHUB_ENV."
107
+ - "Add actionlint to CI — it can detect shell/syntax mismatches in workflow files."
108
+ - "Add an explicit post-step validation that reads back expected outputs to confirm they were written correctly."
109
+ - "Prefer shell: bash globally (via defaults: run: shell: bash) when your workflows target cross-platform and use bash syntax."
110
+ docs:
111
+ - url: "https://stackoverflow.com/questions/74443940/value-not-set-using-github-output"
112
+ label: "Stack Overflow: Value not set using $GITHUB_OUTPUT on Windows PowerShell (Score: 22, 17K views)"
113
+ - url: "https://stackoverflow.com/questions/66733076/github-actions-set-environment-variable-for-windows-build-with-powershell"
114
+ label: "Stack Overflow: Set environment variable for Windows build with PowerShell (Score: 16, 11K views)"
115
+ - url: "https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter"
116
+ label: "GitHub Docs: Setting an output parameter (includes PowerShell examples)"
117
+ - url: "https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#defaultsrun"
118
+ label: "GitHub Docs: defaults.run.shell — set default shell for all run steps"