@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,129 @@
|
|
|
1
|
+
id: triggers-015
|
|
2
|
+
title: "on: push Fires When a Branch Is Deleted — Unguarded Workflows Fail"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- push
|
|
7
|
+
- branch-delete
|
|
8
|
+
- github.event.deleted
|
|
9
|
+
- ref-not-found
|
|
10
|
+
- workflow-trigger
|
|
11
|
+
- branch-cleanup
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "on:\\s*push"
|
|
14
|
+
flags: "im"
|
|
15
|
+
- regex: "github\\.event\\.deleted"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "fatal.*not.*a.*git repository|fatal.*couldn't find remote ref"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "fatal: couldn't find remote ref refs/heads/my-feature-branch"
|
|
21
|
+
- "Error: The process '/usr/bin/git' failed with exit code 128"
|
|
22
|
+
- "fatal: not a git repository (or any of the parent directories): .git"
|
|
23
|
+
- "remote: Repository not found"
|
|
24
|
+
root_cause: |
|
|
25
|
+
GitHub's `push` webhook fires on ALL ref write operations, including branch and tag
|
|
26
|
+
deletions. When a branch is deleted, a push event is emitted with:
|
|
27
|
+
- `github.event.deleted == true`
|
|
28
|
+
- `github.event.created == false`
|
|
29
|
+
- `github.ref` set to the deleted branch ref (e.g., `refs/heads/feature-xyz`)
|
|
30
|
+
- `github.event.after` set to `0000000000000000000000000000000000000000` (40 zeros)
|
|
31
|
+
- `github.sha` reverts to the default branch SHA
|
|
32
|
+
|
|
33
|
+
Workflows triggered by `on: push` that do not guard against deletion events can:
|
|
34
|
+
- Fail when `actions/checkout` tries to check out a branch that no longer exists
|
|
35
|
+
- Trigger deployment pipelines, test suites, or notification workflows unexpectedly
|
|
36
|
+
- Produce misleading failures that look like unrelated CI breakage
|
|
37
|
+
|
|
38
|
+
This is especially problematic in workflows that:
|
|
39
|
+
- Checkout a specific branch ref from `github.ref`
|
|
40
|
+
- Run clean-up scripts expecting the branch to exist
|
|
41
|
+
- Use `git describe` or `git log` on the tip of the deleted branch
|
|
42
|
+
|
|
43
|
+
GitHub documentation notes: "When you delete a branch, the SHA in the workflow run
|
|
44
|
+
(and its associated refs) reverts to the default branch of the repository."
|
|
45
|
+
Source: GitHub Docs — Events that trigger workflows (push event payload)
|
|
46
|
+
Source: GitHub Docs — Webhook events and payloads (`deleted` field on push payload)
|
|
47
|
+
fix: |
|
|
48
|
+
Add an `if:` guard to any job or workflow step that should not run on branch deletions.
|
|
49
|
+
The `github.event.deleted` context field is `true` when the push event is a deletion.
|
|
50
|
+
|
|
51
|
+
For most push-triggered workflows, the intent is to react to new commits — guard with:
|
|
52
|
+
`if: github.event.deleted != true`
|
|
53
|
+
|
|
54
|
+
For tag deletion events, also check `github.event.ref_type` if you use the `delete`
|
|
55
|
+
event, or use `if: github.event.deleted != true && !startsWith(github.ref, 'refs/tags/')`.
|
|
56
|
+
|
|
57
|
+
Alternatively, use the `on: delete` event for branch/tag cleanup workflows instead of
|
|
58
|
+
listening for deletions via the push event.
|
|
59
|
+
fix_code:
|
|
60
|
+
- language: yaml
|
|
61
|
+
label: "Guard push jobs from running on branch deletion"
|
|
62
|
+
code: |
|
|
63
|
+
on:
|
|
64
|
+
push:
|
|
65
|
+
branches:
|
|
66
|
+
- 'feature/**'
|
|
67
|
+
- 'fix/**'
|
|
68
|
+
|
|
69
|
+
jobs:
|
|
70
|
+
build:
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
# ✅ Skip the job entirely when the push is a branch deletion
|
|
73
|
+
if: github.event.deleted != true
|
|
74
|
+
steps:
|
|
75
|
+
- uses: actions/checkout@v4
|
|
76
|
+
- run: npm ci && npm test
|
|
77
|
+
|
|
78
|
+
- language: yaml
|
|
79
|
+
label: "Per-step guard if you need some steps to run on deletion"
|
|
80
|
+
code: |
|
|
81
|
+
jobs:
|
|
82
|
+
process:
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
steps:
|
|
85
|
+
- name: Checkout (skip on delete)
|
|
86
|
+
if: github.event.deleted != true
|
|
87
|
+
uses: actions/checkout@v4
|
|
88
|
+
|
|
89
|
+
- name: Run tests (skip on delete)
|
|
90
|
+
if: github.event.deleted != true
|
|
91
|
+
run: npm test
|
|
92
|
+
|
|
93
|
+
- name: Log branch deleted
|
|
94
|
+
if: github.event.deleted == true
|
|
95
|
+
run: echo "Branch ${{ github.ref_name }} was deleted"
|
|
96
|
+
|
|
97
|
+
- language: yaml
|
|
98
|
+
label: "Use on: delete event for cleanup workflows instead"
|
|
99
|
+
code: |
|
|
100
|
+
# Prefer the dedicated 'delete' event for branch/tag cleanup
|
|
101
|
+
on:
|
|
102
|
+
delete:
|
|
103
|
+
# Optionally filter to branches only (not tags)
|
|
104
|
+
|
|
105
|
+
jobs:
|
|
106
|
+
cleanup:
|
|
107
|
+
runs-on: ubuntu-latest
|
|
108
|
+
# github.event.ref is the deleted branch/tag name
|
|
109
|
+
# github.event.ref_type is 'branch' or 'tag'
|
|
110
|
+
if: github.event.ref_type == 'branch'
|
|
111
|
+
steps:
|
|
112
|
+
- name: Clean up preview environment
|
|
113
|
+
run: |
|
|
114
|
+
echo "Cleaning up for deleted branch: ${{ github.event.ref }}"
|
|
115
|
+
./scripts/teardown-preview.sh "${{ github.event.ref }}"
|
|
116
|
+
|
|
117
|
+
prevention:
|
|
118
|
+
- "Add `if: github.event.deleted != true` to all jobs in `on: push` workflows."
|
|
119
|
+
- "Use the `on: delete` event for branch/tag teardown workflows instead of catching deletions via push."
|
|
120
|
+
- "Check `github.event.after == '0000000000000000000000000000000000000000'` as an alternative deletion guard."
|
|
121
|
+
- "Avoid checking out `${{ github.ref }}` directly; use `actions/checkout` defaults which handle this gracefully."
|
|
122
|
+
- "Add branch filters under `on: push: branches:` to limit which branches trigger the workflow."
|
|
123
|
+
docs:
|
|
124
|
+
- url: "https://docs.github.com/en/webhooks/webhook-events-and-payloads#push"
|
|
125
|
+
label: "GitHub Docs: push webhook event payload (deleted field)"
|
|
126
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push"
|
|
127
|
+
label: "GitHub Docs: push event trigger"
|
|
128
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#delete"
|
|
129
|
+
label: "GitHub Docs: delete event trigger"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
id: triggers-014
|
|
2
|
+
title: "github.event.before Is All Zeros on First Push to a New Branch — git diff Fails"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- push
|
|
7
|
+
- first-push
|
|
8
|
+
- git-diff
|
|
9
|
+
- before-sha
|
|
10
|
+
- new-branch
|
|
11
|
+
- branch-creation
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "fatal.*bad revision.*0000000000000000000000000000000000000000"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "fatal.*ambiguous argument '0{40}'"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "unknown revision or path not in the working tree.*0{10}"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "0{40}\\.\\.\\.\\$\\{\\{.*github\\.event\\.before"
|
|
20
|
+
flags: "i"
|
|
21
|
+
error_messages:
|
|
22
|
+
- "fatal: bad revision '0000000000000000000000000000000000000000...HEAD'"
|
|
23
|
+
- "fatal: ambiguous argument '0000000000000000000000000000000000000000': unknown revision or path not in the working tree"
|
|
24
|
+
- "error: object file .git/objects/00/00000000000000000000000000000000000000 is empty"
|
|
25
|
+
root_cause: |
|
|
26
|
+
When a **new branch is pushed for the first time**, `github.event.before` is set to
|
|
27
|
+
`0000000000000000000000000000000000000000` (40 zeros), representing the null/empty SHA.
|
|
28
|
+
This indicates there was no previous commit on this branch — it is a branch creation event.
|
|
29
|
+
|
|
30
|
+
Workflows that use `github.event.before` to compute a diff range (e.g., to find changed
|
|
31
|
+
files) fail when run on a first push because `git diff` or `git log` cannot resolve the
|
|
32
|
+
null SHA:
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
run: git diff ${{ github.event.before }}...HEAD
|
|
36
|
+
# On first push: git diff 0000000000000000000000000000000000000000...HEAD → FATAL ERROR
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**When this occurs:**
|
|
40
|
+
- First push to a new branch (branch creation)
|
|
41
|
+
- `push` events that create a new branch (`github.event.created == true`)
|
|
42
|
+
- Forced pushes that reference a non-existent before-SHA (rare)
|
|
43
|
+
|
|
44
|
+
**Why it's painful:**
|
|
45
|
+
- Workflows work fine for subsequent pushes (where `before` is a real commit)
|
|
46
|
+
- The error only appears on the first push, often during branch creation in a PR workflow
|
|
47
|
+
- The error message references the git object store internals, not the workflow context
|
|
48
|
+
- Developers check out the diff command syntax without realizing the SHA is invalid
|
|
49
|
+
fix: |
|
|
50
|
+
**Option 1 (recommended): Guard against the null SHA before running git diff**
|
|
51
|
+
|
|
52
|
+
Check if `github.event.before` is the null SHA and fall back to diffing against the
|
|
53
|
+
merge base or the initial commit.
|
|
54
|
+
|
|
55
|
+
**Option 2: Use the `dorny/paths-filter` action**
|
|
56
|
+
|
|
57
|
+
The `dorny/paths-filter` action handles first-push, force-push, and all edge cases
|
|
58
|
+
in branch diff computation automatically.
|
|
59
|
+
|
|
60
|
+
**Option 3: Use `github.event.created` to skip diff-based logic on branch creation**
|
|
61
|
+
|
|
62
|
+
Add an `if: github.event.created != true` guard to diff-dependent steps so they only
|
|
63
|
+
run on non-creation push events.
|
|
64
|
+
fix_code:
|
|
65
|
+
- language: yaml
|
|
66
|
+
label: "Broken — git diff with github.event.before fails on first push"
|
|
67
|
+
code: |
|
|
68
|
+
# ❌ BROKEN: Fails when branch is pushed for the first time
|
|
69
|
+
on: push
|
|
70
|
+
|
|
71
|
+
jobs:
|
|
72
|
+
lint-changed:
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
steps:
|
|
75
|
+
- uses: actions/checkout@v4
|
|
76
|
+
with:
|
|
77
|
+
fetch-depth: 0
|
|
78
|
+
- name: Lint only changed files
|
|
79
|
+
run: |
|
|
80
|
+
CHANGED=$(git diff --name-only ${{ github.event.before }}...HEAD)
|
|
81
|
+
# FATAL on first push: 0000000000000000000000000000000000000000 is not a commit
|
|
82
|
+
- language: yaml
|
|
83
|
+
label: "Fixed — guard against null SHA before git diff"
|
|
84
|
+
code: |
|
|
85
|
+
# ✅ FIXED: Check for null SHA and fall back to first commit
|
|
86
|
+
on: push
|
|
87
|
+
|
|
88
|
+
jobs:
|
|
89
|
+
lint-changed:
|
|
90
|
+
runs-on: ubuntu-latest
|
|
91
|
+
steps:
|
|
92
|
+
- uses: actions/checkout@v4
|
|
93
|
+
with:
|
|
94
|
+
fetch-depth: 0
|
|
95
|
+
- name: Lint only changed files
|
|
96
|
+
run: |
|
|
97
|
+
BEFORE="${{ github.event.before }}"
|
|
98
|
+
NULL_SHA="0000000000000000000000000000000000000000"
|
|
99
|
+
|
|
100
|
+
if [ "$BEFORE" = "$NULL_SHA" ]; then
|
|
101
|
+
# First push to new branch — diff against the initial commit or all files
|
|
102
|
+
echo "First push to branch, diffing all files"
|
|
103
|
+
CHANGED=$(git diff --name-only $(git rev-list --max-parents=0 HEAD)...HEAD)
|
|
104
|
+
else
|
|
105
|
+
CHANGED=$(git diff --name-only "$BEFORE"...HEAD)
|
|
106
|
+
fi
|
|
107
|
+
echo "$CHANGED"
|
|
108
|
+
- language: yaml
|
|
109
|
+
label: "Fixed — skip diff steps on branch creation using github.event.created"
|
|
110
|
+
code: |
|
|
111
|
+
# ✅ FIXED: Skip diff-based steps entirely on first push (branch creation)
|
|
112
|
+
on: push
|
|
113
|
+
|
|
114
|
+
jobs:
|
|
115
|
+
lint-changed:
|
|
116
|
+
runs-on: ubuntu-latest
|
|
117
|
+
steps:
|
|
118
|
+
- uses: actions/checkout@v4
|
|
119
|
+
with:
|
|
120
|
+
fetch-depth: 0
|
|
121
|
+
- name: Lint only changed files
|
|
122
|
+
# Skip on branch creation (before SHA is null)
|
|
123
|
+
if: github.event.created != true
|
|
124
|
+
run: |
|
|
125
|
+
git diff --name-only ${{ github.event.before }}...HEAD | xargs eslint
|
|
126
|
+
- language: yaml
|
|
127
|
+
label: "Fixed — use dorny/paths-filter which handles null SHA automatically"
|
|
128
|
+
code: |
|
|
129
|
+
# ✅ RECOMMENDED: dorny/paths-filter handles first-push and force-push edge cases
|
|
130
|
+
on: push
|
|
131
|
+
|
|
132
|
+
jobs:
|
|
133
|
+
lint-changed:
|
|
134
|
+
runs-on: ubuntu-latest
|
|
135
|
+
steps:
|
|
136
|
+
- uses: actions/checkout@v4
|
|
137
|
+
- uses: dorny/paths-filter@v3
|
|
138
|
+
id: changes
|
|
139
|
+
with:
|
|
140
|
+
filters: |
|
|
141
|
+
src:
|
|
142
|
+
- 'src/**'
|
|
143
|
+
tests:
|
|
144
|
+
- 'tests/**'
|
|
145
|
+
- name: Lint src changes
|
|
146
|
+
if: steps.changes.outputs.src == 'true'
|
|
147
|
+
run: npm run lint
|
|
148
|
+
prevention:
|
|
149
|
+
- "Never use `git diff ${{ github.event.before }}` without guarding against the null SHA (`0000000000000000000000000000000000000000`) — this is guaranteed to fail on first push."
|
|
150
|
+
- "Prefer `dorny/paths-filter` for changed-file detection — it handles all SHA edge cases (first push, force push, merge commits) internally."
|
|
151
|
+
- "If you must use raw git diff, always add: `if [ \"$BEFORE\" = \"$NULL_SHA\" ]; then echo 'first push'; exit 0; fi`."
|
|
152
|
+
- "Test your diff workflows by creating a fresh test branch — don't only test on subsequent pushes where `before` is always valid."
|
|
153
|
+
- "Use `github.event.created == true` in `if:` conditions to identify branch creation events and handle them separately."
|
|
154
|
+
docs:
|
|
155
|
+
- url: "https://docs.github.com/en/webhooks/webhook-events-and-payloads#push"
|
|
156
|
+
label: "GitHub Docs: push webhook event payload — before field"
|
|
157
|
+
- url: "https://github.com/dorny/paths-filter"
|
|
158
|
+
label: "dorny/paths-filter — changed file detection with edge case handling"
|
|
159
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push"
|
|
160
|
+
label: "GitHub Docs: Events that trigger workflows — push"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
id: yaml-syntax-022
|
|
2
|
+
title: "env Context Rejected in continue-on-error and Other Job-Level Boolean Attributes"
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- continue-on-error
|
|
7
|
+
- env-context
|
|
8
|
+
- expression
|
|
9
|
+
- job-level
|
|
10
|
+
- unrecognized-named-value
|
|
11
|
+
- context-availability
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Unrecognized named-value: 'env'"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Unexpected value.*env\\."
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "continue-on-error.*env\\.|env\\..*continue-on-error"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "The workflow is not valid.*Unrecognized named-value: 'env'"
|
|
20
|
+
flags: "i"
|
|
21
|
+
error_messages:
|
|
22
|
+
- "The workflow is not valid. .github/workflows/ci.yml (Line X, Col Y): Unrecognized named-value: 'env'. Located at position 1 within expression: env.MY_FLAG"
|
|
23
|
+
- "Unexpected value '${{ contains(env.MY_LIST, matrix.value) }}'"
|
|
24
|
+
root_cause: |
|
|
25
|
+
The `env` context is NOT available in all workflow attribute positions. Specifically,
|
|
26
|
+
job-level and step-level attributes that accept boolean values or simple flags — such as
|
|
27
|
+
`continue-on-error`, `timeout-minutes` (value expressions), and similar fields — do not
|
|
28
|
+
support the `env` context in their expression evaluations.
|
|
29
|
+
|
|
30
|
+
This is a known GitHub Actions platform limitation documented in actions/runner#1492. The
|
|
31
|
+
error occurs because these attributes are evaluated at workflow parse/validation time (before
|
|
32
|
+
the job runs), when environment variables have not yet been set in the runner context.
|
|
33
|
+
|
|
34
|
+
Contexts available in `continue-on-error`:
|
|
35
|
+
- ✅ `github`, `needs`, `strategy`, `matrix`
|
|
36
|
+
- ❌ `env`, `secrets`, `steps`, `jobs`
|
|
37
|
+
|
|
38
|
+
A common pattern that fails:
|
|
39
|
+
```yaml
|
|
40
|
+
steps:
|
|
41
|
+
- run: ./build.sh
|
|
42
|
+
continue-on-error: ${{ contains(env.SUPPORTED_VERSIONS, matrix.version) }}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Even if `env.SUPPORTED_VERSIONS` was set in the `env:` block at the top of the workflow,
|
|
46
|
+
the expression fails because `env` is not a supported context in that attribute.
|
|
47
|
+
fix: |
|
|
48
|
+
Move the boolean logic into a step output computed earlier in the job, then reference the
|
|
49
|
+
step output in `continue-on-error`. Alternatively, restructure the workflow to avoid needing
|
|
50
|
+
`env`-based expressions in boolean job/step attributes.
|
|
51
|
+
|
|
52
|
+
**Option 1**: Compute the flag in an earlier step and use `steps` context:
|
|
53
|
+
```yaml
|
|
54
|
+
- id: check
|
|
55
|
+
run: echo "should_skip=$([[ "$MY_FLAG" == "true" ]] && echo true || echo false)" >> $GITHUB_OUTPUT
|
|
56
|
+
- run: ./potentially-failing-task.sh
|
|
57
|
+
continue-on-error: ${{ steps.check.outputs.should_skip == 'true' }}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Option 2**: Use `matrix` context directly (supported in continue-on-error):
|
|
61
|
+
```yaml
|
|
62
|
+
strategy:
|
|
63
|
+
matrix:
|
|
64
|
+
experimental: [true, false]
|
|
65
|
+
steps:
|
|
66
|
+
- run: ./build.sh
|
|
67
|
+
continue-on-error: ${{ matrix.experimental }}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Option 3**: Wrap the step in a conditional run script that handles the error inline.
|
|
71
|
+
fix_code:
|
|
72
|
+
- language: yaml
|
|
73
|
+
label: "Use step output to control continue-on-error dynamically"
|
|
74
|
+
code: |
|
|
75
|
+
jobs:
|
|
76
|
+
test:
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
env:
|
|
79
|
+
EXPERIMENTAL_VERSIONS: "3.12 3.13"
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@v4
|
|
82
|
+
|
|
83
|
+
# ❌ This FAILS: env context not available in continue-on-error
|
|
84
|
+
# - run: pytest
|
|
85
|
+
# continue-on-error: ${{ contains(env.EXPERIMENTAL_VERSIONS, matrix.python) }}
|
|
86
|
+
|
|
87
|
+
# ✅ Compute the flag first, then reference via steps context
|
|
88
|
+
- id: flags
|
|
89
|
+
run: |
|
|
90
|
+
if echo "$EXPERIMENTAL_VERSIONS" | grep -qw "${{ matrix.python }}"; then
|
|
91
|
+
echo "experimental=true" >> $GITHUB_OUTPUT
|
|
92
|
+
else
|
|
93
|
+
echo "experimental=false" >> $GITHUB_OUTPUT
|
|
94
|
+
fi
|
|
95
|
+
- run: pytest
|
|
96
|
+
continue-on-error: ${{ steps.flags.outputs.experimental == 'true' }}
|
|
97
|
+
- language: yaml
|
|
98
|
+
label: "Use matrix boolean directly (no env needed)"
|
|
99
|
+
code: |
|
|
100
|
+
jobs:
|
|
101
|
+
test:
|
|
102
|
+
strategy:
|
|
103
|
+
matrix:
|
|
104
|
+
include:
|
|
105
|
+
- python: "3.11"
|
|
106
|
+
experimental: false
|
|
107
|
+
- python: "3.12"
|
|
108
|
+
experimental: true
|
|
109
|
+
- python: "3.13"
|
|
110
|
+
experimental: true
|
|
111
|
+
runs-on: ubuntu-latest
|
|
112
|
+
steps:
|
|
113
|
+
- uses: actions/checkout@v4
|
|
114
|
+
- uses: actions/setup-python@v5
|
|
115
|
+
with:
|
|
116
|
+
python-version: ${{ matrix.python }}
|
|
117
|
+
- run: pytest
|
|
118
|
+
continue-on-error: ${{ matrix.experimental }} # ✅ matrix context IS supported
|
|
119
|
+
prevention:
|
|
120
|
+
- "Check the GitHub Actions context availability table before using any context in job-level attributes — not all contexts are available everywhere."
|
|
121
|
+
- "When you need dynamic boolean flags in `continue-on-error`, compute them in an earlier step and read via `steps.<id>.outputs`."
|
|
122
|
+
- "The `matrix` context is fully supported in `continue-on-error` — prefer encoding experimental flags in the matrix definition."
|
|
123
|
+
- "Run `actionlint` on your workflow to catch context-availability errors before pushing."
|
|
124
|
+
docs:
|
|
125
|
+
- url: "https://github.com/actions/runner/issues/1492"
|
|
126
|
+
label: "actions/runner#1492 — env context not available in continue-on-error (original report)"
|
|
127
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#context-availability"
|
|
128
|
+
label: "GitHub Docs — context availability table (where each context can be used)"
|
|
129
|
+
- url: "https://github.com/rhysd/actionlint"
|
|
130
|
+
label: "actionlint — static analyzer that catches context-availability errors"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
id: yaml-syntax-021
|
|
2
|
+
title: "fromJSON() on empty or undefined step output throws Unexpected end of JSON input"
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- fromjson
|
|
7
|
+
- outputs
|
|
8
|
+
- expression
|
|
9
|
+
- json
|
|
10
|
+
- step-outputs
|
|
11
|
+
- dynamic-matrix
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Unexpected end of JSON input"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Invalid JSON for parameter"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "is not valid JSON"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "fromJSON.*failed"
|
|
20
|
+
flags: "i"
|
|
21
|
+
error_messages:
|
|
22
|
+
- "Unexpected end of JSON input"
|
|
23
|
+
- "Error: Invalid JSON for parameter 'matrix': Unexpected end of JSON input"
|
|
24
|
+
- "Error: The expression result is not valid JSON"
|
|
25
|
+
root_cause: |
|
|
26
|
+
fromJSON() is a GitHub Actions expression function that parses a JSON string.
|
|
27
|
+
When the input string is empty ("") — which happens when a step output was
|
|
28
|
+
never set or when the step that set it was skipped due to an if: condition —
|
|
29
|
+
fromJSON() throws a fatal parse error: "Unexpected end of JSON input".
|
|
30
|
+
|
|
31
|
+
This is especially common in dynamic matrix strategies:
|
|
32
|
+
|
|
33
|
+
matrix:
|
|
34
|
+
include: ${{ fromJSON(steps.generate.outputs.matrix) }}
|
|
35
|
+
|
|
36
|
+
If the generate step was skipped (e.g., if: github.event_name == 'push'),
|
|
37
|
+
steps.generate.outputs.matrix evaluates to empty string, and fromJSON("")
|
|
38
|
+
crashes the entire job before any steps run.
|
|
39
|
+
|
|
40
|
+
The expression evaluator does NOT treat empty string as null or as valid
|
|
41
|
+
empty JSON — it passes the raw empty string directly to the JSON parser,
|
|
42
|
+
which rejects it immediately.
|
|
43
|
+
fix: |
|
|
44
|
+
Provide a fallback JSON value using the || operator so that fromJSON always
|
|
45
|
+
receives valid JSON even when the output is empty:
|
|
46
|
+
|
|
47
|
+
fromJSON(steps.generate.outputs.matrix || '[]')
|
|
48
|
+
fromJSON(steps.generate.outputs.config || '{}')
|
|
49
|
+
|
|
50
|
+
For dynamic matrix strategies, also ensure the generating step always
|
|
51
|
+
outputs valid JSON (at minimum '[]' or '{}') rather than leaving the
|
|
52
|
+
output unset when there is nothing to process.
|
|
53
|
+
|
|
54
|
+
Add an if: condition on downstream jobs to skip entirely when the matrix
|
|
55
|
+
output is empty or equals the empty-array sentinel.
|
|
56
|
+
fix_code:
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: "Guard fromJSON with a valid JSON fallback using the || operator"
|
|
59
|
+
code: |
|
|
60
|
+
jobs:
|
|
61
|
+
compute-matrix:
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
outputs:
|
|
64
|
+
matrix: ${{ steps.gen.outputs.matrix }}
|
|
65
|
+
steps:
|
|
66
|
+
- id: gen
|
|
67
|
+
run: |
|
|
68
|
+
# Always emit valid JSON — even when there is nothing to build
|
|
69
|
+
TARGETS=$(cat targets.json 2>/dev/null || echo '[]')
|
|
70
|
+
echo "matrix=$TARGETS" >> $GITHUB_OUTPUT
|
|
71
|
+
|
|
72
|
+
build:
|
|
73
|
+
needs: compute-matrix
|
|
74
|
+
if: ${{ needs.compute-matrix.outputs.matrix != '[]' }}
|
|
75
|
+
strategy:
|
|
76
|
+
matrix:
|
|
77
|
+
# fromJSON never sees empty string — falls back to []
|
|
78
|
+
include: ${{ fromJSON(needs.compute-matrix.outputs.matrix || '[]') }}
|
|
79
|
+
runs-on: ubuntu-latest
|
|
80
|
+
steps:
|
|
81
|
+
- run: echo "Building ${{ matrix.target }}"
|
|
82
|
+
- language: yaml
|
|
83
|
+
label: "Inline fallback for dynamic matrix include"
|
|
84
|
+
code: |
|
|
85
|
+
strategy:
|
|
86
|
+
matrix:
|
|
87
|
+
include: ${{ fromJSON(needs.setup.outputs.matrix || '[{"env":"default"}]') }}
|
|
88
|
+
prevention:
|
|
89
|
+
- "Always provide a valid JSON fallback: fromJSON(steps.x.outputs.val || '{}')"
|
|
90
|
+
- "Ensure output-producing steps always write a value even if it is an empty array or object"
|
|
91
|
+
- "Add if: conditions to skip downstream jobs entirely when the matrix output is empty"
|
|
92
|
+
- "Validate fromJSON expressions with an empty-output test scenario in a draft workflow"
|
|
93
|
+
docs:
|
|
94
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#fromjson"
|
|
95
|
+
label: "GitHub Docs — fromJSON expression function"
|
|
96
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs"
|
|
97
|
+
label: "GitHub Docs — Passing information between jobs using outputs"
|
|
98
|
+
- url: "https://github.com/orgs/community/discussions/26671"
|
|
99
|
+
label: "GitHub Community #26671 — fromJSON fails when output is empty string"
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
id: yaml-syntax-020
|
|
2
|
+
title: "if: !cancelled() Triggers YAML Tag Parse Error — Must Quote or Use Expression Syntax"
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- if-condition
|
|
7
|
+
- cancelled
|
|
8
|
+
- yaml-tag
|
|
9
|
+
- expression-syntax
|
|
10
|
+
- negation
|
|
11
|
+
- bang-operator
|
|
12
|
+
- always-run
|
|
13
|
+
- parse-error
|
|
14
|
+
patterns:
|
|
15
|
+
- regex: "if:\\s*!(?:cancelled|failure|success|always)\\(\\)"
|
|
16
|
+
flags: "im"
|
|
17
|
+
- regex: "mapping values are not allowed here|did not find expected key|could not find expected"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "yaml.*tag.*!\\w+|Unknown tag"
|
|
20
|
+
flags: "i"
|
|
21
|
+
error_messages:
|
|
22
|
+
- "mapping values are not allowed here"
|
|
23
|
+
- "could not find expected ':'"
|
|
24
|
+
- "did not find expected key"
|
|
25
|
+
- "Error: YAMLException: bad indentation of a mapping entry"
|
|
26
|
+
- "Invalid workflow file: You have an error in your yaml syntax"
|
|
27
|
+
root_cause: |
|
|
28
|
+
In YAML, `!` is a reserved indicator used to specify **explicit type tags**
|
|
29
|
+
(e.g., `!!str`, `!python/object`). When you write an unquoted `if:` value starting
|
|
30
|
+
with `!`, the YAML parser interprets it as a tag application — NOT as boolean negation.
|
|
31
|
+
|
|
32
|
+
For example:
|
|
33
|
+
```
|
|
34
|
+
if: !cancelled() # YAML reads this as: apply tag "cancelled()" to null value
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Because `!cancelled()` is an invalid YAML tag application (no value follows the tag,
|
|
38
|
+
or the parentheses make it syntactically invalid as a tag identifier), this causes a
|
|
39
|
+
YAML parse error and the entire workflow file is rejected with messages like
|
|
40
|
+
"mapping values are not allowed here" or "bad indentation of a mapping entry".
|
|
41
|
+
|
|
42
|
+
This is a common mistake because developers familiar with programming languages expect
|
|
43
|
+
`!` to be a boolean NOT operator, which it is inside GitHub Actions expressions
|
|
44
|
+
(`${{ !cancelled() }}`), but NOT in bare YAML string values.
|
|
45
|
+
|
|
46
|
+
Affected operators:
|
|
47
|
+
- `if: !cancelled()` → YAML tag error
|
|
48
|
+
- `if: !failure()` → YAML tag error
|
|
49
|
+
- `if: !success()` → YAML tag error (rare, but same pattern)
|
|
50
|
+
|
|
51
|
+
The fix is either:
|
|
52
|
+
1. Wrap in expression delimiters: `if: ${{ !cancelled() }}`
|
|
53
|
+
2. Quote the value: `if: "!cancelled()"`
|
|
54
|
+
Both forms are valid GitHub Actions `if:` conditions.
|
|
55
|
+
|
|
56
|
+
Source: GitHub Community Discussion — common YAML parse error with if: conditions
|
|
57
|
+
Source: GitHub Actions documentation — Expressions syntax
|
|
58
|
+
fix: |
|
|
59
|
+
Choose one of two equivalent fixes:
|
|
60
|
+
|
|
61
|
+
Option 1 — Use expression syntax (recommended, more explicit):
|
|
62
|
+
`if: ${{ !cancelled() }}`
|
|
63
|
+
|
|
64
|
+
Option 2 — Quote the string (also valid):
|
|
65
|
+
`if: "!cancelled()"`
|
|
66
|
+
|
|
67
|
+
Both options tell the YAML parser to treat the value as a plain string / expression,
|
|
68
|
+
bypassing the tag interpretation.
|
|
69
|
+
|
|
70
|
+
Note: Inside `${{ }}` expression blocks, `!` IS the boolean NOT operator and works
|
|
71
|
+
as expected. Outside those blocks, bare YAML values starting with `!` are parsed as
|
|
72
|
+
YAML type tags.
|
|
73
|
+
fix_code:
|
|
74
|
+
- language: yaml
|
|
75
|
+
label: "Fix: use ${{ }} expression syntax for negation"
|
|
76
|
+
code: |
|
|
77
|
+
jobs:
|
|
78
|
+
cleanup:
|
|
79
|
+
runs-on: ubuntu-latest
|
|
80
|
+
steps:
|
|
81
|
+
# ❌ WRONG: YAML parse error — ! is a YAML tag indicator
|
|
82
|
+
- name: Run unless cancelled (broken)
|
|
83
|
+
if: !cancelled()
|
|
84
|
+
run: ./cleanup.sh
|
|
85
|
+
|
|
86
|
+
# ✅ CORRECT: wrap in expression delimiters
|
|
87
|
+
- name: Run unless cancelled (fixed)
|
|
88
|
+
if: ${{ !cancelled() }}
|
|
89
|
+
run: ./cleanup.sh
|
|
90
|
+
|
|
91
|
+
# ✅ ALSO CORRECT: quote the string
|
|
92
|
+
- name: Run unless cancelled (also fixed)
|
|
93
|
+
if: "!cancelled()"
|
|
94
|
+
run: ./cleanup.sh
|
|
95
|
+
|
|
96
|
+
- language: yaml
|
|
97
|
+
label: "Common patterns: negating status check functions"
|
|
98
|
+
code: |
|
|
99
|
+
jobs:
|
|
100
|
+
notify:
|
|
101
|
+
runs-on: ubuntu-latest
|
|
102
|
+
steps:
|
|
103
|
+
# Run step if previous steps did NOT fail
|
|
104
|
+
- name: Send success notification
|
|
105
|
+
if: ${{ !failure() }}
|
|
106
|
+
run: ./notify-success.sh
|
|
107
|
+
|
|
108
|
+
# Run if job was NOT cancelled (catch failures too)
|
|
109
|
+
- name: Always-ish cleanup (skips on cancel)
|
|
110
|
+
if: ${{ !cancelled() }}
|
|
111
|
+
run: ./cleanup.sh
|
|
112
|
+
|
|
113
|
+
# Job-level condition: run this job if parent was not skipped or failed
|
|
114
|
+
# (equivalent to always() but excluding explicitly-failed states)
|
|
115
|
+
|
|
116
|
+
- language: yaml
|
|
117
|
+
label: "Using always() instead of !cancelled() when appropriate"
|
|
118
|
+
code: |
|
|
119
|
+
jobs:
|
|
120
|
+
report:
|
|
121
|
+
runs-on: ubuntu-latest
|
|
122
|
+
# always() runs regardless of success, failure, OR cancellation
|
|
123
|
+
# !cancelled() runs on success OR failure, but NOT cancellation
|
|
124
|
+
steps:
|
|
125
|
+
- name: Report always (even if cancelled)
|
|
126
|
+
if: always()
|
|
127
|
+
run: ./post-report.sh
|
|
128
|
+
|
|
129
|
+
- name: Report unless cancelled
|
|
130
|
+
if: ${{ !cancelled() }}
|
|
131
|
+
run: ./post-report-unless-cancelled.sh
|
|
132
|
+
|
|
133
|
+
prevention:
|
|
134
|
+
- "Always wrap boolean expressions using `!`, `&&`, or `||` inside `${{ }}` delimiters in `if:` conditions."
|
|
135
|
+
- "Quote any `if:` value that starts with `!` if you prefer not to use expression syntax."
|
|
136
|
+
- "Use `always()` when you want to run regardless of success, failure, AND cancellation."
|
|
137
|
+
- "Use `!cancelled()` (inside `${{ }}`) when you want to run on success or failure, but not cancellation."
|
|
138
|
+
- "Lint workflows with `actionlint` locally — it catches YAML tag parse errors before push."
|
|
139
|
+
docs:
|
|
140
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#operators"
|
|
141
|
+
label: "GitHub Docs: Expressions — operators (! boolean NOT)"
|
|
142
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#status-check-functions"
|
|
143
|
+
label: "GitHub Docs: Expressions — status check functions (cancelled, failure, success)"
|
|
144
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsif"
|
|
145
|
+
label: "GitHub Docs: Workflow syntax — steps.if"
|