@htekdev/actions-debugger 1.0.14 → 1.0.16
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/dist/db/search.js +3 -1
- package/dist/db/search.js.map +1 -1
- package/dist/tools/suggest-fix.d.ts.map +1 -1
- package/dist/tools/suggest-fix.js +5 -1
- package/dist/tools/suggest-fix.js.map +1 -1
- package/errors/caching-artifacts/cache-key-too-long.yml +93 -0
- package/errors/caching-artifacts/cache-path-not-exist-skipped.yml +152 -0
- package/errors/caching-artifacts/cache-save-same-key-html-conflict.yml +109 -0
- package/errors/caching-artifacts/docker-buildx-gha-cache-capacity.yml +107 -0
- package/errors/caching-artifacts/setup-ruby-bundler-ephemeral-workdir-cache-miss.yml +147 -0
- package/errors/caching-artifacts/upload-artifact-v3-retirement-blocked.yml +123 -0
- package/errors/caching-artifacts/upload-artifact-v4-large-file-macos-hang.yml +111 -0
- package/errors/concurrency-timing/always-cleanup-5min-forced-kill.yml +140 -0
- package/errors/concurrency-timing/concurrency-group-env-context-undefined.yml +99 -0
- package/errors/concurrency-timing/required-check-pending-path-filter-skip.yml +160 -0
- package/errors/concurrency-timing/wait-timer-cancel-in-progress-starvation.yml +125 -0
- package/errors/known-unsolved/composite-action-step-timeout-minutes-ignored.yml +146 -0
- package/errors/known-unsolved/reusable-workflow-no-composite-action-call.yml +116 -0
- package/errors/known-unsolved/schedule-trigger-default-branch-only.yml +113 -0
- package/errors/known-unsolved/secrets-not-allowed-in-if-conditions.yml +149 -0
- package/errors/known-unsolved/workflow-50-rerun-limit.yml +110 -0
- package/errors/permissions-auth/check-run-status-modification-blocked.yml +134 -0
- package/errors/permissions-auth/dependabot-pr-secrets-unavailable.yml +133 -0
- package/errors/permissions-auth/fine-grained-pat-deployment-write-required.yml +146 -0
- package/errors/permissions-auth/github-app-installation-token-new-format.yml +124 -0
- package/errors/permissions-auth/github-packages-read-requires-packages-permission.yml +128 -0
- package/errors/permissions-auth/oidc-id-token-write-permission-missing.yml +169 -0
- package/errors/permissions-auth/permissions-empty-block-removes-contents-read.yml +97 -0
- package/errors/permissions-auth/reusable-workflow-permissions-not-inherited.yml +114 -0
- package/errors/runner-environment/checkout-windows-ebusy-lock.yml +124 -0
- package/errors/runner-environment/deprecated-action-version-auto-rejected.yml +89 -0
- package/errors/runner-environment/github-hosted-runner-disk-space-full.yml +85 -0
- package/errors/runner-environment/github-path-same-step-not-found.yml +114 -0
- package/errors/runner-environment/github-script-v6-octokit-rest-actions-not-function.yml +87 -0
- package/errors/runner-environment/macos-13-deprecation-brownout.yml +93 -0
- package/errors/runner-environment/macos-15-mono-nuget-removed.yml +151 -0
- package/errors/runner-environment/macos-15-xcode-simulator-sdk-policy.yml +141 -0
- package/errors/runner-environment/multi-runtime-nov2025-removal.yml +120 -0
- package/errors/runner-environment/runner-oom-exit-code-137.yml +117 -0
- package/errors/runner-environment/setup-go-go123-telemetry-cache-failure.yml +92 -0
- package/errors/runner-environment/setup-java-distribution-required.yml +108 -0
- package/errors/runner-environment/ubuntu-2004-retirement-brownout.yml +107 -0
- package/errors/runner-environment/windows-latest-d-drive-removed.yml +104 -0
- package/errors/runner-environment/windows-vs2026-cuda-host-compiler-unsupported.yml +145 -0
- package/errors/silent-failures/event-commits-empty-on-workflow-dispatch.yml +110 -0
- package/errors/silent-failures/fetch-tags-depth-one-silent-no-op.yml +77 -0
- package/errors/silent-failures/github-env-multiline-value-truncated.yml +127 -0
- package/errors/silent-failures/github-sha-pr-merge-commit-not-head.yml +150 -0
- package/errors/silent-failures/job-output-masked-as-secret-empty.yml +147 -0
- package/errors/silent-failures/upload-artifact-permissions-stripped.yml +98 -0
- package/errors/triggers/pull-request-branches-filter-matches-base-not-head.yml +140 -0
- package/errors/triggers/push-event-fires-on-branch-delete.yml +129 -0
- package/errors/triggers/push-first-commit-before-sha-zeros.yml +160 -0
- package/errors/yaml-syntax/continue-on-error-env-context-rejected.yml +130 -0
- package/errors/yaml-syntax/fromjson-empty-string-crash.yml +99 -0
- package/errors/yaml-syntax/if-bang-negation-yaml-tag.yml +145 -0
- package/errors/yaml-syntax/local-action-path-always-top-level.yml +142 -0
- package/package.json +1 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
id: silent-failures-019
|
|
2
|
+
title: "GITHUB_ENV Multiline Values Silently Truncate Without Heredoc Delimiter Format"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- GITHUB_ENV
|
|
7
|
+
- environment-variable
|
|
8
|
+
- multiline
|
|
9
|
+
- heredoc
|
|
10
|
+
- truncation
|
|
11
|
+
- env-file
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "GITHUB_ENV.*multiline|multiline.*GITHUB_ENV"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Invalid format '.*\\n'"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "Error: Failed to parse environment file"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Error: Failed to parse environment file"
|
|
21
|
+
- "Invalid format 'MY_VAR=line1\nline2'"
|
|
22
|
+
- "Warning: Environment file exporting failed"
|
|
23
|
+
root_cause: |
|
|
24
|
+
When setting an environment variable with `$GITHUB_ENV` that contains **newline
|
|
25
|
+
characters**, naïvely echoing the value produces an invalid environment file entry
|
|
26
|
+
that is silently truncated or causes a parse error.
|
|
27
|
+
|
|
28
|
+
The environment file format used by GitHub Actions expects single-line `KEY=VALUE`
|
|
29
|
+
entries. If a value contains a literal newline, the runner cannot parse it and
|
|
30
|
+
typically:
|
|
31
|
+
- Silently truncates the value at the first newline (the variable gets only the
|
|
32
|
+
first line)
|
|
33
|
+
- In strict runner versions: fails with "Error: Failed to parse environment file"
|
|
34
|
+
|
|
35
|
+
**Common incorrect pattern:**
|
|
36
|
+
```yaml
|
|
37
|
+
run: echo "MY_CERT=${{ secrets.CERT }}" >> $GITHUB_ENV
|
|
38
|
+
# If CERT contains newlines, the env file entry is broken
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Why it's a silent failure:**
|
|
42
|
+
- The step succeeds with exit code 0
|
|
43
|
+
- No obvious error message appears in the logs
|
|
44
|
+
- Downstream steps see a truncated, incomplete variable value
|
|
45
|
+
- Certificate/JSON/SSH key values are commonly multi-line and hit this silently
|
|
46
|
+
|
|
47
|
+
This is separate from the `$GITHUB_OUTPUT` multiline issue (which uses the same
|
|
48
|
+
heredoc syntax fix) — the root cause and pattern are identical but the environment
|
|
49
|
+
file (`GITHUB_ENV`) is a distinct file from the output file (`GITHUB_OUTPUT`).
|
|
50
|
+
|
|
51
|
+
Sources: GitHub Community #160171, GitHub Docs workflow commands
|
|
52
|
+
fix: |
|
|
53
|
+
Use the **heredoc delimiter format** for multiline values in `$GITHUB_ENV`:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
KEY<<DELIMITER
|
|
57
|
+
value-line-1
|
|
58
|
+
value-line-2
|
|
59
|
+
DELIMITER
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The delimiter can be any string that does not appear in the value. `EOF` is the
|
|
63
|
+
conventional choice. The delimiter must appear on its own line both as the opening
|
|
64
|
+
marker and the closing marker.
|
|
65
|
+
|
|
66
|
+
This same pattern applies to `$GITHUB_OUTPUT` for multiline step outputs.
|
|
67
|
+
fix_code:
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: "Broken — newline in value breaks the env file silently"
|
|
70
|
+
code: |
|
|
71
|
+
# ❌ BROKEN: Secret or cert value with newlines truncates silently
|
|
72
|
+
- name: Set multiline env var
|
|
73
|
+
run: echo "MY_CERT=${{ secrets.TLS_CERT }}" >> $GITHUB_ENV
|
|
74
|
+
# Result: MY_CERT contains only the first line of the certificate
|
|
75
|
+
- language: yaml
|
|
76
|
+
label: "Fixed — heredoc delimiter format for multiline GITHUB_ENV values"
|
|
77
|
+
code: |
|
|
78
|
+
# ✅ FIXED: Use heredoc delimiter syntax
|
|
79
|
+
- name: Set multiline env var
|
|
80
|
+
run: |
|
|
81
|
+
echo "MY_CERT<<EOF" >> $GITHUB_ENV
|
|
82
|
+
echo "${{ secrets.TLS_CERT }}" >> $GITHUB_ENV
|
|
83
|
+
echo "EOF" >> $GITHUB_ENV
|
|
84
|
+
- language: yaml
|
|
85
|
+
label: "Fixed — multiline JSON body in GITHUB_ENV"
|
|
86
|
+
code: |
|
|
87
|
+
# ✅ FIXED: Multi-line JSON stored as env var using heredoc format
|
|
88
|
+
- name: Set JSON config variable
|
|
89
|
+
run: |
|
|
90
|
+
echo "APP_CONFIG<<EOF" >> $GITHUB_ENV
|
|
91
|
+
cat config/settings.json >> $GITHUB_ENV
|
|
92
|
+
echo "EOF" >> $GITHUB_ENV
|
|
93
|
+
- language: yaml
|
|
94
|
+
label: "Fixed — same pattern applies to GITHUB_OUTPUT for multiline outputs"
|
|
95
|
+
code: |
|
|
96
|
+
# ✅ SAME PATTERN for step outputs (GITHUB_OUTPUT)
|
|
97
|
+
- name: Set multiline output
|
|
98
|
+
id: generate
|
|
99
|
+
run: |
|
|
100
|
+
echo "report<<EOF" >> $GITHUB_OUTPUT
|
|
101
|
+
cat test-results.txt >> $GITHUB_OUTPUT
|
|
102
|
+
echo "EOF" >> $GITHUB_OUTPUT
|
|
103
|
+
- language: yaml
|
|
104
|
+
label: "Fixed — PowerShell equivalent for Windows runners"
|
|
105
|
+
code: |
|
|
106
|
+
# ✅ FIXED (Windows/PowerShell): Use Out-File -Append with heredoc-style writes
|
|
107
|
+
- name: Set multiline env var on Windows
|
|
108
|
+
shell: pwsh
|
|
109
|
+
run: |
|
|
110
|
+
@"
|
|
111
|
+
MY_CERT<<EOF
|
|
112
|
+
${{ secrets.TLS_CERT }}
|
|
113
|
+
EOF
|
|
114
|
+
"@ | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
|
115
|
+
prevention:
|
|
116
|
+
- "Always use the `KEY<<DELIMITER ... DELIMITER` heredoc format for any env var value that may contain newlines (certs, JWTs, JSON, SSH keys)."
|
|
117
|
+
- "Never use a single `echo KEY=VALUE >> $GITHUB_ENV` for values sourced from secrets or file contents — they are likely to contain newlines."
|
|
118
|
+
- "The same heredoc pattern applies to `$GITHUB_OUTPUT` — treat them identically for multiline values."
|
|
119
|
+
- "Validate that downstream steps receive the full value: `run: echo \"MY_CERT=$MY_CERT\"` — a truncated cert will be obviously short."
|
|
120
|
+
- "Use `echo 'key<<EOF' >> $GITHUB_ENV` (single quotes around key<<EOF) to avoid accidental shell expansion of the delimiter line."
|
|
121
|
+
docs:
|
|
122
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#multiline-strings"
|
|
123
|
+
label: "GitHub Docs: Workflow commands — Multiline strings in environment files"
|
|
124
|
+
- url: "https://github.com/orgs/community/discussions/160171"
|
|
125
|
+
label: "GitHub Community #160171 — GITHUB_ENV multiline values"
|
|
126
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-an-environment-variable"
|
|
127
|
+
label: "GitHub Docs: Setting an environment variable (GITHUB_ENV format)"
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
id: silent-failures-021
|
|
2
|
+
title: "GITHUB_SHA on pull_request Is the Synthetic Merge Commit — Not the PR Head Commit"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- pull_request
|
|
7
|
+
- github-sha
|
|
8
|
+
- GITHUB_SHA
|
|
9
|
+
- merge-commit
|
|
10
|
+
- head-sha
|
|
11
|
+
- docker-tag
|
|
12
|
+
- commit-lookup
|
|
13
|
+
- git-show
|
|
14
|
+
patterns:
|
|
15
|
+
- regex: "github\\.sha"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "GITHUB_SHA"
|
|
18
|
+
flags: ""
|
|
19
|
+
- regex: "git show \\$\\{\\{\\s*github\\.sha\\s*\\}\\}"
|
|
20
|
+
flags: "i"
|
|
21
|
+
- regex: "commit.*not found|unknown revision or path not in the working tree"
|
|
22
|
+
flags: "i"
|
|
23
|
+
error_messages:
|
|
24
|
+
- "fatal: ambiguous argument 'abc1234ef56': unknown revision or path not in the working tree"
|
|
25
|
+
- "Error: An object named 'abc1234' is not in the repository"
|
|
26
|
+
- "error: pathspec 'abc1234ef56' did not match any file(s) known to git"
|
|
27
|
+
- "commit not found: abc1234ef5678"
|
|
28
|
+
root_cause: |
|
|
29
|
+
When a workflow is triggered by the `pull_request` (or `pull_request_target`) event,
|
|
30
|
+
`github.sha` and `GITHUB_SHA` do NOT contain the SHA of the PR author's latest commit.
|
|
31
|
+
Instead, they contain the SHA of a **synthetic merge commit** — a prospective merge of
|
|
32
|
+
the PR head branch into the target branch, created by GitHub on the fly as
|
|
33
|
+
`refs/pull/<number>/merge`.
|
|
34
|
+
|
|
35
|
+
This merge commit:
|
|
36
|
+
- Exists only as a special GitHub ref, not as a real commit in the repo's history
|
|
37
|
+
- Is NOT returned by `git log` on the PR branch
|
|
38
|
+
- Cannot be resolved by `git show <sha>` after a shallow fetch-depth:1 checkout
|
|
39
|
+
- Has a different SHA than the PR author's actual commit
|
|
40
|
+
|
|
41
|
+
This silently breaks common patterns:
|
|
42
|
+
- Docker image tagging: `docker build -t myimage:${{ github.sha }}` then looking up
|
|
43
|
+
that SHA in CI/CD systems finds nothing in the actual branch history
|
|
44
|
+
- Commit status APIs: posting status to `github.sha` may fail or post to an
|
|
45
|
+
unexpected ref
|
|
46
|
+
- Manual `git log --pretty=format:'%H' | grep ${{ github.sha }}` returns nothing
|
|
47
|
+
- `git show ${{ github.sha }}` after shallow clone fails with "unknown revision"
|
|
48
|
+
|
|
49
|
+
The correct variable to get the PR author's head commit SHA is:
|
|
50
|
+
`github.event.pull_request.head.sha`
|
|
51
|
+
|
|
52
|
+
For `workflow_run` events, use:
|
|
53
|
+
`github.event.workflow_run.head_sha`
|
|
54
|
+
|
|
55
|
+
This behavior is documented in the GitHub environment variables reference but not
|
|
56
|
+
prominently called out, leading to widespread confusion.
|
|
57
|
+
Source: github/docs#15302 — "GITHUB_SHA on PRs refers to the merge commit"
|
|
58
|
+
Source: github/docs#17767 — "github.sha description is misleading for pull_request"
|
|
59
|
+
Source: github/docs#30093 — equivalent of github.sha for other events
|
|
60
|
+
fix: |
|
|
61
|
+
Replace `github.sha` with `github.event.pull_request.head.sha` whenever you need
|
|
62
|
+
the actual PR author's commit SHA in a `pull_request`-triggered workflow.
|
|
63
|
+
|
|
64
|
+
For `workflow_run`, use `github.event.workflow_run.head_sha`.
|
|
65
|
+
For `push` events, `github.sha` is correct (it IS the pushed commit's SHA).
|
|
66
|
+
|
|
67
|
+
If your workflow must be compatible with multiple event types, add a step to compute
|
|
68
|
+
the correct SHA once and expose it via `GITHUB_OUTPUT`:
|
|
69
|
+
```
|
|
70
|
+
COMMIT_SHA=${{ github.event.pull_request.head.sha || github.sha }}
|
|
71
|
+
```
|
|
72
|
+
Note: The `||` expression operator works in GitHub Actions expressions —
|
|
73
|
+
`github.event.pull_request.head.sha` is empty for non-PR events, so `github.sha`
|
|
74
|
+
becomes the fallback.
|
|
75
|
+
fix_code:
|
|
76
|
+
- language: yaml
|
|
77
|
+
label: "Fix: use github.event.pull_request.head.sha for PR head commit"
|
|
78
|
+
code: |
|
|
79
|
+
on: pull_request
|
|
80
|
+
|
|
81
|
+
jobs:
|
|
82
|
+
build:
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/checkout@v4
|
|
86
|
+
|
|
87
|
+
# ❌ WRONG: github.sha is the merge commit SHA, not the PR head commit
|
|
88
|
+
- name: Tag Docker image (wrong SHA)
|
|
89
|
+
run: docker build -t myimage:${{ github.sha }} .
|
|
90
|
+
|
|
91
|
+
# ✅ CORRECT: use the PR head commit SHA
|
|
92
|
+
- name: Tag Docker image (correct SHA)
|
|
93
|
+
run: docker build -t myimage:${{ github.event.pull_request.head.sha }} .
|
|
94
|
+
|
|
95
|
+
- language: yaml
|
|
96
|
+
label: "Cross-event SHA: works for both push and pull_request"
|
|
97
|
+
code: |
|
|
98
|
+
on:
|
|
99
|
+
push:
|
|
100
|
+
pull_request:
|
|
101
|
+
|
|
102
|
+
jobs:
|
|
103
|
+
build:
|
|
104
|
+
runs-on: ubuntu-latest
|
|
105
|
+
steps:
|
|
106
|
+
- uses: actions/checkout@v4
|
|
107
|
+
|
|
108
|
+
- name: Resolve correct commit SHA
|
|
109
|
+
id: sha
|
|
110
|
+
run: |
|
|
111
|
+
# For pull_request: use head.sha; for push: fall back to github.sha
|
|
112
|
+
COMMIT_SHA="${{ github.event.pull_request.head.sha || github.sha }}"
|
|
113
|
+
echo "commit_sha=$COMMIT_SHA" >> "$GITHUB_OUTPUT"
|
|
114
|
+
|
|
115
|
+
- name: Use resolved SHA
|
|
116
|
+
run: docker build -t myimage:${{ steps.sha.outputs.commit_sha }} .
|
|
117
|
+
|
|
118
|
+
- language: yaml
|
|
119
|
+
label: "workflow_run: use head_sha from the triggering workflow"
|
|
120
|
+
code: |
|
|
121
|
+
on:
|
|
122
|
+
workflow_run:
|
|
123
|
+
workflows: [CI]
|
|
124
|
+
types: [completed]
|
|
125
|
+
|
|
126
|
+
jobs:
|
|
127
|
+
deploy:
|
|
128
|
+
runs-on: ubuntu-latest
|
|
129
|
+
steps:
|
|
130
|
+
# ❌ WRONG: github.sha on workflow_run is the default branch SHA
|
|
131
|
+
- run: echo "Wrong SHA: ${{ github.sha }}"
|
|
132
|
+
|
|
133
|
+
# ✅ CORRECT: head_sha is the SHA of the commit that triggered the upstream workflow
|
|
134
|
+
- run: echo "Correct SHA: ${{ github.event.workflow_run.head_sha }}"
|
|
135
|
+
|
|
136
|
+
prevention:
|
|
137
|
+
- "Never use `github.sha` directly in `pull_request` workflows — always use `github.event.pull_request.head.sha`."
|
|
138
|
+
- "Add a comment in your workflow YAML calling out which SHA variable to use for which event."
|
|
139
|
+
- "For Docker tagging and commit status APIs in PR workflows, always derive the SHA from the event payload."
|
|
140
|
+
- "Add a debug step printing both `github.sha` and `github.event.pull_request.head.sha` when troubleshooting."
|
|
141
|
+
- "For `workflow_run` events, use `github.event.workflow_run.head_sha` not `github.sha`."
|
|
142
|
+
docs:
|
|
143
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#github-context"
|
|
144
|
+
label: "GitHub Docs: github context — github.sha"
|
|
145
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request"
|
|
146
|
+
label: "GitHub Docs: pull_request event (GITHUB_SHA value)"
|
|
147
|
+
- url: "https://github.com/github/docs/issues/15302"
|
|
148
|
+
label: "github/docs#15302: GITHUB_SHA on PRs refers to the merge commit"
|
|
149
|
+
- url: "https://github.com/github/docs/issues/17767"
|
|
150
|
+
label: "github/docs#17767: github.sha description is misleading for pull_request"
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
id: silent-failures-020
|
|
2
|
+
title: "Job Output Silently Emptied by Secret Masking — Downstream Steps See Empty String"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- job-outputs
|
|
7
|
+
- secrets
|
|
8
|
+
- masking
|
|
9
|
+
- empty-output
|
|
10
|
+
- runner
|
|
11
|
+
- cryptic
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Value.*masked.*job output"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "\\[MASKED\\].*output"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "Warning.*masked.*not be set as.*output"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "(no error shown — downstream job receives empty string instead of expected value)"
|
|
21
|
+
- "Warning: Value is masked and will not be set as an output"
|
|
22
|
+
root_cause: |
|
|
23
|
+
The GitHub Actions runner applies secret masking to **job outputs**. If a job output
|
|
24
|
+
value contains a substring that matches a registered secret (or any value added via
|
|
25
|
+
`add-mask`), the runner replaces the output value with `***` (the masked placeholder)
|
|
26
|
+
before passing it to dependent jobs.
|
|
27
|
+
|
|
28
|
+
When this happens, the downstream job reads the output as an **empty string** (or the
|
|
29
|
+
literal `***` which evaluates as empty in most conditional checks), producing a silent
|
|
30
|
+
failure with no error in the logs — the upload/set step exits 0, but the value is gone.
|
|
31
|
+
|
|
32
|
+
**Triggers:**
|
|
33
|
+
- Outputting a value that happens to contain a secret substring (e.g., a build artifact
|
|
34
|
+
path that includes a secret-derived token)
|
|
35
|
+
- Using `echo "::add-mask::$VALUE"` and then emitting `$VALUE` as a job output
|
|
36
|
+
- Dynamic values (UUIDs, tokens, generated keys) that partially overlap masked values
|
|
37
|
+
|
|
38
|
+
**Why it's silent:**
|
|
39
|
+
- The `set-output` / `$GITHUB_OUTPUT` step exits with code 0
|
|
40
|
+
- The parent job shows "success"
|
|
41
|
+
- No "masked" warning appears in the UI by default
|
|
42
|
+
- The dependent job simply receives `""` and often silently skips dependent work
|
|
43
|
+
|
|
44
|
+
Sources: actions/runner#1498, GitHub Community discussions/37942
|
|
45
|
+
fix: |
|
|
46
|
+
**Option 1 (recommended): Encode output before setting, decode before using**
|
|
47
|
+
|
|
48
|
+
Base64-encode the value before writing it to `$GITHUB_OUTPUT`. Decode it in the
|
|
49
|
+
consuming job. Base64 strings do not match secret substrings.
|
|
50
|
+
|
|
51
|
+
**Option 2: Avoid passing secret-derived values as job outputs**
|
|
52
|
+
|
|
53
|
+
Restructure the workflow so secrets are consumed directly in the job that has access
|
|
54
|
+
to them rather than forwarded through outputs.
|
|
55
|
+
|
|
56
|
+
**Option 3: Check for masking with a debug step**
|
|
57
|
+
|
|
58
|
+
Add a step that prints the raw output value to confirm it is not masked before
|
|
59
|
+
downstream jobs consume it.
|
|
60
|
+
|
|
61
|
+
**Option 4: Use artifact upload instead of job outputs for large/sensitive values**
|
|
62
|
+
|
|
63
|
+
Upload the value as a workflow artifact (with restricted retention) and download it
|
|
64
|
+
in dependent jobs — artifacts bypass the masking pipeline.
|
|
65
|
+
fix_code:
|
|
66
|
+
- language: yaml
|
|
67
|
+
label: "Broken — output value matches secret pattern, silently emptied"
|
|
68
|
+
code: |
|
|
69
|
+
# ❌ BROKEN: If 'generated-token' overlaps with a masked secret, output is empty
|
|
70
|
+
jobs:
|
|
71
|
+
generate:
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
outputs:
|
|
74
|
+
token: ${{ steps.gen.outputs.token }}
|
|
75
|
+
steps:
|
|
76
|
+
- id: gen
|
|
77
|
+
run: echo "token=$SOME_DERIVED_VALUE" >> $GITHUB_OUTPUT
|
|
78
|
+
|
|
79
|
+
consume:
|
|
80
|
+
needs: generate
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
steps:
|
|
83
|
+
- run: echo "Token is ${{ needs.generate.outputs.token }}"
|
|
84
|
+
# Prints: "Token is " — silently empty, no error
|
|
85
|
+
- language: yaml
|
|
86
|
+
label: "Fixed — base64-encode output to avoid masking collision"
|
|
87
|
+
code: |
|
|
88
|
+
# ✅ FIXED: Encode before output, decode before use
|
|
89
|
+
jobs:
|
|
90
|
+
generate:
|
|
91
|
+
runs-on: ubuntu-latest
|
|
92
|
+
outputs:
|
|
93
|
+
token_b64: ${{ steps.gen.outputs.token_b64 }}
|
|
94
|
+
steps:
|
|
95
|
+
- id: gen
|
|
96
|
+
run: |
|
|
97
|
+
DERIVED_VALUE="$(generate-my-token)"
|
|
98
|
+
# Encode so masking patterns don't match
|
|
99
|
+
echo "token_b64=$(echo -n "$DERIVED_VALUE" | base64)" >> $GITHUB_OUTPUT
|
|
100
|
+
|
|
101
|
+
consume:
|
|
102
|
+
needs: generate
|
|
103
|
+
runs-on: ubuntu-latest
|
|
104
|
+
steps:
|
|
105
|
+
- run: |
|
|
106
|
+
# Decode in the consumer job
|
|
107
|
+
TOKEN=$(echo "${{ needs.generate.outputs.token_b64 }}" | base64 -d)
|
|
108
|
+
echo "Token: $TOKEN"
|
|
109
|
+
- language: yaml
|
|
110
|
+
label: "Fixed — pass value via artifact instead of job output"
|
|
111
|
+
code: |
|
|
112
|
+
# ✅ ALTERNATIVE: Use artifact upload to avoid masking pipeline entirely
|
|
113
|
+
jobs:
|
|
114
|
+
generate:
|
|
115
|
+
runs-on: ubuntu-latest
|
|
116
|
+
steps:
|
|
117
|
+
- id: gen
|
|
118
|
+
run: generate-my-token > token.txt
|
|
119
|
+
- uses: actions/upload-artifact@v4
|
|
120
|
+
with:
|
|
121
|
+
name: generated-token
|
|
122
|
+
path: token.txt
|
|
123
|
+
retention-days: 1
|
|
124
|
+
|
|
125
|
+
consume:
|
|
126
|
+
needs: generate
|
|
127
|
+
runs-on: ubuntu-latest
|
|
128
|
+
steps:
|
|
129
|
+
- uses: actions/download-artifact@v4
|
|
130
|
+
with:
|
|
131
|
+
name: generated-token
|
|
132
|
+
- run: TOKEN=$(cat token.txt) && echo "Token: $TOKEN"
|
|
133
|
+
prevention:
|
|
134
|
+
- "Never pass values through job outputs that contain or partially overlap with known secret values — use artifacts or re-derive them in consuming jobs."
|
|
135
|
+
- "If a job output starts arriving empty with no error, add a debug step to print `${{ steps.ID.outputs.KEY }}` immediately after the output is set — if it shows `***`, masking is the culprit."
|
|
136
|
+
- "Avoid `add-mask` on values you later need to pass as job outputs — once masked, the value cannot be safely forwarded."
|
|
137
|
+
- "Use base64 encoding as a general defensive practice for any dynamic/computed value passed through `$GITHUB_OUTPUT` across jobs."
|
|
138
|
+
- "Monitor GitHub runner release notes — the masking heuristics have changed across runner versions and may affect previously working workflows."
|
|
139
|
+
docs:
|
|
140
|
+
- url: "https://github.com/actions/runner/issues/1498"
|
|
141
|
+
label: "actions/runner#1498 — Job output emptied by secret masking"
|
|
142
|
+
- url: "https://github.com/orgs/community/discussions/37942"
|
|
143
|
+
label: "GitHub Community #37942 — Combining job outputs with masking leads to empty output"
|
|
144
|
+
- url: "https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#redacting-secrets-in-logs"
|
|
145
|
+
label: "GitHub Docs: Redacting secrets in logs"
|
|
146
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs"
|
|
147
|
+
label: "GitHub Docs: Passing information between jobs"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
id: silent-failures-023
|
|
2
|
+
title: "upload-artifact Silently Strips Unix File Permissions (chmod +x Lost)"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- upload-artifact
|
|
7
|
+
- download-artifact
|
|
8
|
+
- file-permissions
|
|
9
|
+
- chmod
|
|
10
|
+
- executable
|
|
11
|
+
- linux
|
|
12
|
+
- macos
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "Permission denied"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "EACCES: permission denied"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "cannot execute.*Permission denied"
|
|
19
|
+
flags: "i"
|
|
20
|
+
error_messages:
|
|
21
|
+
- "/bin/sh: ./script.sh: Permission denied"
|
|
22
|
+
- "Error: EACCES: permission denied, access '/path/to/binary'"
|
|
23
|
+
- "bash: ./build/my-binary: Permission denied"
|
|
24
|
+
- "Process completed with exit code 126"
|
|
25
|
+
root_cause: |
|
|
26
|
+
The `actions/upload-artifact` action zips files using a Node.js-based zip implementation
|
|
27
|
+
that does not preserve Unix file permission mode bits. When the artifact is downloaded in
|
|
28
|
+
a subsequent job with `actions/download-artifact`, all files are extracted with default
|
|
29
|
+
permissions (typically 0644), silently losing any executable bits or other custom permissions
|
|
30
|
+
that were set in the producing job.
|
|
31
|
+
|
|
32
|
+
This affects any workflow that:
|
|
33
|
+
- Builds a binary in one job and runs it in a downstream job
|
|
34
|
+
- Produces shell scripts with +x and executes them after download
|
|
35
|
+
- Compiles Python wheels with C extensions requiring executable shared libraries
|
|
36
|
+
- Uses tools that detect file mode bits (e.g., `test -x ./binary`)
|
|
37
|
+
|
|
38
|
+
The upload and download both complete with no warnings. The failure only surfaces when the
|
|
39
|
+
downloaded file is executed in the downstream job. This is a known open issue since 2020
|
|
40
|
+
affecting both `upload-artifact@v3` and `v4`.
|
|
41
|
+
fix: |
|
|
42
|
+
Wrap files in a tar archive before uploading. Since the tar utility preserves Unix permission
|
|
43
|
+
mode bits, permissions survive the upload/download cycle. Alternatively, re-apply permissions
|
|
44
|
+
explicitly with `chmod` in the downstream job after downloading.
|
|
45
|
+
fix_code:
|
|
46
|
+
- language: yaml
|
|
47
|
+
label: "Preserve permissions with tar (recommended)"
|
|
48
|
+
code: |
|
|
49
|
+
# --- Job A: Build and upload ---
|
|
50
|
+
jobs:
|
|
51
|
+
build:
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
steps:
|
|
54
|
+
- name: Build binary
|
|
55
|
+
run: |
|
|
56
|
+
make my-binary
|
|
57
|
+
chmod +x my-binary
|
|
58
|
+
|
|
59
|
+
- name: Package with tar to preserve permissions
|
|
60
|
+
run: tar -czf artifacts.tar.gz my-binary
|
|
61
|
+
|
|
62
|
+
- uses: actions/upload-artifact@v4
|
|
63
|
+
with:
|
|
64
|
+
name: my-artifacts
|
|
65
|
+
path: artifacts.tar.gz
|
|
66
|
+
|
|
67
|
+
# --- Job B: Download and run ---
|
|
68
|
+
deploy:
|
|
69
|
+
needs: build
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/download-artifact@v4
|
|
73
|
+
with:
|
|
74
|
+
name: my-artifacts
|
|
75
|
+
|
|
76
|
+
- name: Extract and run (permissions restored)
|
|
77
|
+
run: |
|
|
78
|
+
tar -xzf artifacts.tar.gz
|
|
79
|
+
./my-binary
|
|
80
|
+
- language: yaml
|
|
81
|
+
label: "Re-apply chmod after download"
|
|
82
|
+
code: |
|
|
83
|
+
- uses: actions/download-artifact@v4
|
|
84
|
+
with:
|
|
85
|
+
name: my-artifacts
|
|
86
|
+
|
|
87
|
+
- name: Restore executable permissions
|
|
88
|
+
run: chmod +x my-binary ./scripts/*.sh
|
|
89
|
+
prevention:
|
|
90
|
+
- "Never rely on upload-artifact to preserve Unix file permissions — always tar or re-chmod"
|
|
91
|
+
- "If your downstream job runs a downloaded binary, add `chmod +x` before executing it as a safety habit"
|
|
92
|
+
- "Use `ls -la` after download as a debug step to verify permissions are what you expect"
|
|
93
|
+
- "For permission-sensitive artifacts, `actions/cache` can be used as an alternative — it preserves permissions"
|
|
94
|
+
docs:
|
|
95
|
+
- url: "https://github.com/actions/upload-artifact/issues/38"
|
|
96
|
+
label: "actions/upload-artifact#38 — upload-artifact does not retain artifact permissions (169 reactions)"
|
|
97
|
+
- url: "https://github.com/actions/download-artifact/issues/14"
|
|
98
|
+
label: "actions/download-artifact#14 — download-artifact loses permissions on download (54 reactions)"
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
id: triggers-013
|
|
2
|
+
title: "pull_request branches Filter Matches Base (Target) Branch, Not Head (Source) Branch"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- pull-request
|
|
7
|
+
- branches-filter
|
|
8
|
+
- base-branch
|
|
9
|
+
- head-branch
|
|
10
|
+
- trigger
|
|
11
|
+
- silent-failure
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "branches.*pull_request.*head_ref"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "on.*pull_request.*branches"
|
|
16
|
+
flags: "i"
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Workflow not triggered for pull request from feature branch"
|
|
19
|
+
- "Workflow triggered unexpectedly for pull request to main"
|
|
20
|
+
root_cause: |
|
|
21
|
+
The `branches:` filter under `on.pull_request` matches the **target (base) branch**
|
|
22
|
+
of the pull request — i.e., the branch being merged INTO — NOT the source (head)
|
|
23
|
+
branch the PR was created from.
|
|
24
|
+
|
|
25
|
+
This is the opposite of what many developers expect. Common mistakes:
|
|
26
|
+
|
|
27
|
+
1. **"Run CI only for PRs from feature branches"** — You write:
|
|
28
|
+
```yaml
|
|
29
|
+
on:
|
|
30
|
+
pull_request:
|
|
31
|
+
branches: ['feature/**']
|
|
32
|
+
```
|
|
33
|
+
This does NOT trigger for PRs *from* feature branches. It triggers for PRs
|
|
34
|
+
*targeting* branches matching `feature/**`. Most feature branches are opened
|
|
35
|
+
targeting `main` or `develop`, so this filter silently prevents CI from running.
|
|
36
|
+
|
|
37
|
+
2. **"Run CI only when targeting main"** — You write the correct filter but test
|
|
38
|
+
with a PR from `main` to a release branch and are surprised it fires.
|
|
39
|
+
|
|
40
|
+
3. **branches-ignore on pull_request** — Same inversion: `branches-ignore` ignores
|
|
41
|
+
based on the TARGET branch, not the source branch.
|
|
42
|
+
|
|
43
|
+
There is no built-in filter for the source (head) branch of a pull request at
|
|
44
|
+
the trigger level. The `on.pull_request.branches` filter only controls the
|
|
45
|
+
target/base branch.
|
|
46
|
+
|
|
47
|
+
Sources: GitHub Community #26795, Stack Overflow #70101203
|
|
48
|
+
fix: |
|
|
49
|
+
**To filter by the TARGET (base) branch** (where the PR merges to):
|
|
50
|
+
Use `on.pull_request.branches:` — this is the intended behavior.
|
|
51
|
+
|
|
52
|
+
**To filter by the SOURCE (head) branch** (where the PR was created from):
|
|
53
|
+
You cannot do this at the `on:` trigger level. Instead, add an `if:` condition on
|
|
54
|
+
the job or step using `github.head_ref`:
|
|
55
|
+
|
|
56
|
+
`if: startsWith(github.head_ref, 'feature/')`
|
|
57
|
+
|
|
58
|
+
**To trigger only on PRs targeting main:**
|
|
59
|
+
```yaml
|
|
60
|
+
on:
|
|
61
|
+
pull_request:
|
|
62
|
+
branches: [main]
|
|
63
|
+
```
|
|
64
|
+
This is correct — branches: [main] means "PRs whose base is main".
|
|
65
|
+
fix_code:
|
|
66
|
+
- language: yaml
|
|
67
|
+
label: "Broken — developer intends to filter by head/source branch but branches: filters base"
|
|
68
|
+
code: |
|
|
69
|
+
# ❌ BROKEN: developer wants CI only for PRs from 'feature/**' branches
|
|
70
|
+
# but branches: matches TARGET branch, not SOURCE branch
|
|
71
|
+
# This workflow will NOT trigger for "feature/my-feat → main" PRs
|
|
72
|
+
# (because 'main' doesn't match 'feature/**')
|
|
73
|
+
on:
|
|
74
|
+
pull_request:
|
|
75
|
+
branches:
|
|
76
|
+
- 'feature/**' # This matches the BASE branch, not the head branch!
|
|
77
|
+
- language: yaml
|
|
78
|
+
label: "Fixed — use if: condition on github.head_ref to filter by source branch"
|
|
79
|
+
code: |
|
|
80
|
+
# ✅ FIXED: trigger on all PRs, then conditionally run based on head_ref
|
|
81
|
+
on:
|
|
82
|
+
pull_request:
|
|
83
|
+
|
|
84
|
+
jobs:
|
|
85
|
+
ci:
|
|
86
|
+
# Only run for PRs from feature branches (filtering by HEAD/source branch)
|
|
87
|
+
if: startsWith(github.head_ref, 'feature/')
|
|
88
|
+
runs-on: ubuntu-latest
|
|
89
|
+
steps:
|
|
90
|
+
- uses: actions/checkout@v4
|
|
91
|
+
- run: npm test
|
|
92
|
+
- language: yaml
|
|
93
|
+
label: "Correct — branches: to filter by TARGET branch (the intended use case)"
|
|
94
|
+
code: |
|
|
95
|
+
# ✅ CORRECT: run CI only for PRs targeting main or release branches
|
|
96
|
+
# (branches: matches the BASE/target branch — the branch being merged INTO)
|
|
97
|
+
on:
|
|
98
|
+
pull_request:
|
|
99
|
+
branches:
|
|
100
|
+
- main
|
|
101
|
+
- 'release/**'
|
|
102
|
+
|
|
103
|
+
jobs:
|
|
104
|
+
ci:
|
|
105
|
+
runs-on: ubuntu-latest
|
|
106
|
+
steps:
|
|
107
|
+
- uses: actions/checkout@v4
|
|
108
|
+
- run: npm test
|
|
109
|
+
- language: yaml
|
|
110
|
+
label: "Both filters — target main AND from a feature branch"
|
|
111
|
+
code: |
|
|
112
|
+
# ✅ Combine trigger-level base filter with job-level head_ref condition
|
|
113
|
+
on:
|
|
114
|
+
pull_request:
|
|
115
|
+
branches:
|
|
116
|
+
- main # Only for PRs targeting main
|
|
117
|
+
|
|
118
|
+
jobs:
|
|
119
|
+
ci:
|
|
120
|
+
# Further restrict to feature/* source branches only
|
|
121
|
+
if: startsWith(github.head_ref, 'feature/')
|
|
122
|
+
runs-on: ubuntu-latest
|
|
123
|
+
steps:
|
|
124
|
+
- uses: actions/checkout@v4
|
|
125
|
+
- run: npm test
|
|
126
|
+
prevention:
|
|
127
|
+
- "Remember: `on.pull_request.branches` matches the BASE (target) branch — the branch being merged into."
|
|
128
|
+
- "To filter by the source branch, use `if: startsWith(github.head_ref, 'your-prefix/')` at the job level."
|
|
129
|
+
- "Test your trigger config by opening a test PR and checking the Actions tab — verify the workflow does/doesn't trigger as expected."
|
|
130
|
+
- "`github.base_ref` = target branch name. `github.head_ref` = source branch name. Know the difference."
|
|
131
|
+
- "Use `branches-ignore:` to exclude PRs targeting specific branches (e.g., skip CI for PRs targeting a `docs` branch)."
|
|
132
|
+
docs:
|
|
133
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request"
|
|
134
|
+
label: "pull_request event — branches filter behavior"
|
|
135
|
+
- url: "https://github.com/orgs/community/discussions/26795"
|
|
136
|
+
label: "GitHub Community #26795 — branches filter matches base not head"
|
|
137
|
+
- url: "https://stackoverflow.com/questions/70101203/github-actions-workflow-syntax-not-working-as-expected"
|
|
138
|
+
label: "Stack Overflow #70101203 — pull_request branches filter confusion"
|
|
139
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/using-filters#using-filters-to-target-specific-branches-for-pull-request-events"
|
|
140
|
+
label: "Using filters to target specific branches for pull request events"
|