@htekdev/actions-debugger 1.0.13 → 1.0.15
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/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/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/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/az-powershell-14-to-15-breaking.yml +108 -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-15-mono-nuget-removed.yml +151 -0
- package/errors/runner-environment/macos-15-xcode-simulator-sdk-policy.yml +141 -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-2204-precached-docker-removed.yml +110 -0
- package/errors/runner-environment/windows-latest-d-drive-removed.yml +104 -0
- package/errors/runner-environment/windows-msvc-ltcg-mixed-image-versions.yml +112 -0
- package/errors/runner-environment/windows-vs2026-cuda-host-compiler-unsupported.yml +145 -0
- package/errors/silent-failures/app-store-ios26-sdk-required.yml +113 -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/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,123 @@
|
|
|
1
|
+
id: caching-artifacts-016
|
|
2
|
+
title: "actions/upload-artifact v3 Automatically Blocked After January 2025 Retirement"
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- upload-artifact
|
|
7
|
+
- download-artifact
|
|
8
|
+
- v3
|
|
9
|
+
- deprecated
|
|
10
|
+
- retirement
|
|
11
|
+
- brownout
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "automatically failed.*deprecated.*version.*upload-artifact"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "This request has been automatically failed because it uses a deprecated version"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "upload-artifact.*v3.*deprecated"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "download-artifact.*v3.*deprecated"
|
|
20
|
+
flags: "i"
|
|
21
|
+
error_messages:
|
|
22
|
+
- "This request has been automatically failed because it uses a deprecated version of actions/upload-artifact: v3"
|
|
23
|
+
- "This request has been automatically failed because it uses a deprecated version of actions/download-artifact: v3"
|
|
24
|
+
root_cause: |
|
|
25
|
+
GitHub retired **actions/upload-artifact@v3** and **actions/download-artifact@v3**
|
|
26
|
+
on January 30, 2025. After the retirement date, any workflow still calling v3 of
|
|
27
|
+
these actions receives an immediate hard failure at the step level — the action
|
|
28
|
+
does not run; instead, the runner returns:
|
|
29
|
+
|
|
30
|
+
"This request has been automatically failed because it uses a deprecated
|
|
31
|
+
version of actions/upload-artifact: v3"
|
|
32
|
+
|
|
33
|
+
**Timeline:**
|
|
34
|
+
- April 16, 2024 — GitHub announced v3 deprecation and scheduled retirement
|
|
35
|
+
- November 2024 → January 2025 — Brownout periods (random scheduled failures)
|
|
36
|
+
- January 30, 2025 — Full retirement: all v3 calls blocked unconditionally
|
|
37
|
+
|
|
38
|
+
**Why repos are still affected:**
|
|
39
|
+
- Many CI configurations were written before the deprecation announcement and
|
|
40
|
+
were never updated
|
|
41
|
+
- Reusable workflows called from other orgs/repos may reference v3 internally
|
|
42
|
+
- Third-party action marketplace actions that internally use v3 as a dependency
|
|
43
|
+
were broken until their own maintainers upgraded
|
|
44
|
+
- Workflows with infrequent trigger schedules (e.g., monthly releases) only hit
|
|
45
|
+
the brownout windows occasionally, masking the problem until full retirement
|
|
46
|
+
|
|
47
|
+
Source: GitHub Changelog 2024-04-16, community discussions/149325
|
|
48
|
+
fix: |
|
|
49
|
+
Upgrade both upload and download steps to v4 simultaneously. Do NOT mix v3 and v4
|
|
50
|
+
in the same workflow — they use different artifact backends and are not cross-compatible.
|
|
51
|
+
|
|
52
|
+
**Key v4 behavior changes to be aware of:**
|
|
53
|
+
- Artifact names must be unique per workflow run (v4 does NOT overwrite; throws 409)
|
|
54
|
+
- Hidden files (dotfiles) are excluded by default — set `include-hidden-files: true`
|
|
55
|
+
if you need them
|
|
56
|
+
- Cross-repo artifact access requires explicit permissions
|
|
57
|
+
- GHES instances older than 3.15 do not support v4 — pin to v3 only if on old GHES
|
|
58
|
+
(but old GHES has its own known issues)
|
|
59
|
+
fix_code:
|
|
60
|
+
- language: yaml
|
|
61
|
+
label: "Migrate upload and download to v4 (minimal change)"
|
|
62
|
+
code: |
|
|
63
|
+
jobs:
|
|
64
|
+
build:
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v4
|
|
68
|
+
- run: npm run build
|
|
69
|
+
|
|
70
|
+
# ❌ Retired — will auto-fail after Jan 30, 2025
|
|
71
|
+
# - uses: actions/upload-artifact@v3
|
|
72
|
+
# with:
|
|
73
|
+
# name: dist
|
|
74
|
+
# path: dist/
|
|
75
|
+
|
|
76
|
+
# ✅ Use v4
|
|
77
|
+
- uses: actions/upload-artifact@v4
|
|
78
|
+
with:
|
|
79
|
+
name: dist
|
|
80
|
+
path: dist/
|
|
81
|
+
|
|
82
|
+
deploy:
|
|
83
|
+
needs: build
|
|
84
|
+
runs-on: ubuntu-latest
|
|
85
|
+
steps:
|
|
86
|
+
# ✅ Download also on v4 — must match upload version
|
|
87
|
+
- uses: actions/download-artifact@v4
|
|
88
|
+
with:
|
|
89
|
+
name: dist
|
|
90
|
+
path: dist/
|
|
91
|
+
- language: yaml
|
|
92
|
+
label: "Handle v4 duplicate-name conflict if multiple jobs upload the same name"
|
|
93
|
+
code: |
|
|
94
|
+
jobs:
|
|
95
|
+
build:
|
|
96
|
+
strategy:
|
|
97
|
+
matrix:
|
|
98
|
+
target: [linux, windows, macos]
|
|
99
|
+
runs-on: ubuntu-latest
|
|
100
|
+
steps:
|
|
101
|
+
- run: echo "Build ${{ matrix.target }}" > output.txt
|
|
102
|
+
|
|
103
|
+
# v4: artifact names must be unique per run
|
|
104
|
+
- uses: actions/upload-artifact@v4
|
|
105
|
+
with:
|
|
106
|
+
# Append matrix value to keep names unique
|
|
107
|
+
name: output-${{ matrix.target }}
|
|
108
|
+
path: output.txt
|
|
109
|
+
prevention:
|
|
110
|
+
- "Run `grep -r 'upload-artifact@v3\\|download-artifact@v3' .github/` periodically to catch stale version pins."
|
|
111
|
+
- "Use Dependabot or Renovate to automatically open PRs when GitHub-maintained actions release new major versions."
|
|
112
|
+
- "Subscribe to the GitHub Changelog (https://github.blog/changelog/) for deprecation notices."
|
|
113
|
+
- "When upgrading to v4, test artifact names for uniqueness — v4 throws HTTP 409 when the same name is uploaded twice in one run."
|
|
114
|
+
- "Set `retention-days` explicitly on v4 artifacts; default retention changed between v3 and v4."
|
|
115
|
+
docs:
|
|
116
|
+
- url: "https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions"
|
|
117
|
+
label: "GitHub Changelog: Deprecation notice — v3 of the artifact actions"
|
|
118
|
+
- url: "https://github.com/orgs/community/discussions/149325"
|
|
119
|
+
label: "Community discussion — workflows failing after artifact v3 retirement"
|
|
120
|
+
- url: "https://github.com/actions/upload-artifact/blob/main/docs/MIGRATION.md"
|
|
121
|
+
label: "actions/upload-artifact — v3 to v4 migration guide"
|
|
122
|
+
- url: "https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts"
|
|
123
|
+
label: "GitHub Docs: Storing workflow data as artifacts"
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
id: concurrency-timing-011
|
|
2
|
+
title: "always() Cleanup Jobs Forcibly Killed After 5-Minute Cancellation Timeout"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- always
|
|
7
|
+
- cancellation
|
|
8
|
+
- cleanup
|
|
9
|
+
- forced-termination
|
|
10
|
+
- notification
|
|
11
|
+
- timeout
|
|
12
|
+
- teardown
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "The runner has received a shutdown signal"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "Job was cancelled"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "The operation was canceled"
|
|
19
|
+
flags: "i"
|
|
20
|
+
error_messages:
|
|
21
|
+
- "The runner has received a shutdown signal. This can happen when the runner service is stopped, a new job is started, or the runner is in the process of shutting down."
|
|
22
|
+
- "Job was cancelled"
|
|
23
|
+
root_cause: |
|
|
24
|
+
When a workflow run is cancelled (manually or via `cancel-in-progress`), GitHub Actions
|
|
25
|
+
re-evaluates the `if:` condition for every currently running job. Jobs marked with
|
|
26
|
+
`if: always()` continue running — this is the intended mechanism for cleanup, notifications,
|
|
27
|
+
and teardown steps.
|
|
28
|
+
|
|
29
|
+
However, GitHub enforces a **5-minute hard termination window** after cancellation is
|
|
30
|
+
initiated. Once 5 minutes have elapsed since the cancellation signal, ALL remaining jobs
|
|
31
|
+
are forcibly killed by the server, regardless of their `if:` conditions — including jobs
|
|
32
|
+
explicitly marked `if: always()`.
|
|
33
|
+
|
|
34
|
+
This means:
|
|
35
|
+
- Cleanup jobs that take more than 5 minutes (Terraform destroy, test result uploads,
|
|
36
|
+
Slack notifications with retries, database teardown) will be killed mid-execution.
|
|
37
|
+
- The job may appear partially completed in the logs with no clear failure message —
|
|
38
|
+
it simply stops, often leaving infrastructure in a partial or inconsistent state.
|
|
39
|
+
- Developers are surprised that `always()` does not guarantee the job completes after
|
|
40
|
+
a workflow cancellation.
|
|
41
|
+
|
|
42
|
+
Common failure scenarios:
|
|
43
|
+
- Artifact upload in an `if: always()` post-job step when the upload is slow
|
|
44
|
+
- Terraform `destroy` as a cleanup job when a long-running deployment is cancelled
|
|
45
|
+
- Notification jobs that retry on transient failures and consume more time than expected
|
|
46
|
+
- Integration test teardown (database resets, container removal) that exceeds 5 minutes
|
|
47
|
+
|
|
48
|
+
Source: GitHub Docs — Canceling a workflow: "After the 5 minute cancellation timeout
|
|
49
|
+
period, the server will forcibly terminate all jobs that are still running."
|
|
50
|
+
fix: |
|
|
51
|
+
Design `always()` cleanup jobs to complete well within 5 minutes. Add a job-level
|
|
52
|
+
`timeout-minutes: 4` to any cleanup job that runs after cancellation so it fails
|
|
53
|
+
cleanly rather than being force-killed at an unpredictable point.
|
|
54
|
+
|
|
55
|
+
For teardown that cannot be shortened, trigger cleanup from a separate workflow using
|
|
56
|
+
`workflow_run: [completed]` — it runs after the cancelled run fully settles and is
|
|
57
|
+
not subject to the 5-minute window.
|
|
58
|
+
|
|
59
|
+
Use the `cancelled()` expression to detect cancellation and take a fast code path.
|
|
60
|
+
fix_code:
|
|
61
|
+
- language: yaml
|
|
62
|
+
label: "Guard cleanup job with timeout-minutes to fail fast before forced kill"
|
|
63
|
+
code: |
|
|
64
|
+
jobs:
|
|
65
|
+
deploy:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
timeout-minutes: 60
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
- run: ./deploy.sh
|
|
71
|
+
|
|
72
|
+
cleanup:
|
|
73
|
+
needs: deploy
|
|
74
|
+
if: always()
|
|
75
|
+
runs-on: ubuntu-latest
|
|
76
|
+
timeout-minutes: 4 # Stay under the 5-min forced-kill window
|
|
77
|
+
steps:
|
|
78
|
+
- name: Teardown infrastructure
|
|
79
|
+
run: ./teardown.sh
|
|
80
|
+
timeout-minutes: 3 # Per-step guard too
|
|
81
|
+
|
|
82
|
+
- language: yaml
|
|
83
|
+
label: "Use cancelled() to take a fast notification path on cancellation"
|
|
84
|
+
code: |
|
|
85
|
+
jobs:
|
|
86
|
+
build:
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
steps:
|
|
89
|
+
- run: ./slow-build.sh
|
|
90
|
+
|
|
91
|
+
notify:
|
|
92
|
+
needs: build
|
|
93
|
+
if: always()
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
steps:
|
|
96
|
+
- name: Quick notification (cancellation — must be fast)
|
|
97
|
+
if: cancelled()
|
|
98
|
+
run: |
|
|
99
|
+
curl -s -X POST "$SLACK_WEBHOOK" \
|
|
100
|
+
-H 'Content-type: application/json' \
|
|
101
|
+
-d '{"text":"⚠️ Workflow cancelled — cleanup may be incomplete"}'
|
|
102
|
+
|
|
103
|
+
- name: Full notification (success or failure path — has time)
|
|
104
|
+
if: "!cancelled()"
|
|
105
|
+
run: ./full-notify.sh "${{ needs.build.result }}"
|
|
106
|
+
|
|
107
|
+
- language: yaml
|
|
108
|
+
label: "Post-cancellation teardown via workflow_run — not subject to 5-min window"
|
|
109
|
+
code: |
|
|
110
|
+
# cleanup.yml — separate workflow triggered after any completion including cancellation
|
|
111
|
+
on:
|
|
112
|
+
workflow_run:
|
|
113
|
+
workflows: ["Deploy"]
|
|
114
|
+
types: [completed]
|
|
115
|
+
|
|
116
|
+
jobs:
|
|
117
|
+
teardown:
|
|
118
|
+
runs-on: ubuntu-latest
|
|
119
|
+
steps:
|
|
120
|
+
- uses: actions/checkout@v4
|
|
121
|
+
|
|
122
|
+
- name: Emergency cleanup when deploy was cancelled
|
|
123
|
+
if: github.event.workflow_run.conclusion == 'cancelled'
|
|
124
|
+
run: ./emergency-teardown.sh
|
|
125
|
+
|
|
126
|
+
- name: Normal cleanup on success or failure
|
|
127
|
+
if: github.event.workflow_run.conclusion != 'cancelled'
|
|
128
|
+
run: ./standard-teardown.sh
|
|
129
|
+
prevention:
|
|
130
|
+
- "Keep `if: always()` cleanup jobs under 4 minutes — add `timeout-minutes: 4` as a safety guard."
|
|
131
|
+
- "Use `if: cancelled()` to detect cancellation and take a fast code path rather than the full teardown path."
|
|
132
|
+
- "For cleanup that takes longer than 5 minutes, use a separate `workflow_run: [completed]` workflow that runs outside the cancellation window."
|
|
133
|
+
- "Test cancellation behavior by manually cancelling a long-running workflow and verifying cleanup jobs complete before 5 minutes."
|
|
134
|
+
docs:
|
|
135
|
+
- url: "https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/canceling-a-workflow"
|
|
136
|
+
label: "GitHub Docs: Canceling a workflow (5-minute forced termination)"
|
|
137
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#status-check-functions"
|
|
138
|
+
label: "Status check functions: always(), cancelled()"
|
|
139
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run"
|
|
140
|
+
label: "workflow_run event — trigger cleanup after completed workflows"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
id: concurrency-timing-010
|
|
2
|
+
title: "env Context Unavailable in Concurrency Group Expression Collapses All Runs"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- concurrency
|
|
7
|
+
- env-context
|
|
8
|
+
- expression
|
|
9
|
+
- silent-failure
|
|
10
|
+
- group-collision
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "Canceling since a higher priority waiting"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "concurrency.*group.*\"\""
|
|
15
|
+
flags: "i"
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Canceling since a higher priority waiting request for '' exists"
|
|
18
|
+
- "Canceling since a higher priority waiting run was found for ''"
|
|
19
|
+
root_cause: |
|
|
20
|
+
The `concurrency.group` expression is evaluated at workflow scheduling time, before
|
|
21
|
+
most runtime contexts are available. The `env` context is one of the contexts that
|
|
22
|
+
is NOT available when concurrency expressions are evaluated.
|
|
23
|
+
|
|
24
|
+
When you use `${{ env.MY_VAR }}` in a concurrency group key:
|
|
25
|
+
- The expression silently evaluates to an empty string `""`
|
|
26
|
+
- Every workflow run (across all branches, all events) shares the same group: `""`
|
|
27
|
+
- Runs from completely unrelated branches cancel each other unexpectedly
|
|
28
|
+
- The runner may emit "Canceling since a higher priority waiting request for '' exists"
|
|
29
|
+
with an empty group name — which is the giveaway
|
|
30
|
+
|
|
31
|
+
Contexts available in `concurrency.group`: `github`, `inputs`, `vars`
|
|
32
|
+
Contexts NOT available: `env`, `steps`, `job`, `runner`, `secrets`, `matrix`, `needs`
|
|
33
|
+
|
|
34
|
+
This is a documented limitation but easy to miss because the expression evaluates
|
|
35
|
+
silently without error — it just returns empty string.
|
|
36
|
+
|
|
37
|
+
Sources: GitHub Community #26308, #45734, #69704
|
|
38
|
+
fix: |
|
|
39
|
+
Replace `env` context references in concurrency group expressions with supported
|
|
40
|
+
contexts. Use `github` (event properties, ref, workflow name), `inputs` (for
|
|
41
|
+
workflow_dispatch or workflow_call), or `vars` (repository/org variables).
|
|
42
|
+
|
|
43
|
+
For environment-specific group keys, use `github.event_name`, `github.ref_name`,
|
|
44
|
+
`github.workflow`, or pass an explicit input to workflow_dispatch.
|
|
45
|
+
fix_code:
|
|
46
|
+
- language: yaml
|
|
47
|
+
label: "Broken — env context evaluates to empty string in concurrency group"
|
|
48
|
+
code: |
|
|
49
|
+
# ❌ BROKEN: ${{ env.ENVIRONMENT }} returns "" at scheduling time
|
|
50
|
+
env:
|
|
51
|
+
ENVIRONMENT: production
|
|
52
|
+
|
|
53
|
+
concurrency:
|
|
54
|
+
group: deploy-${{ env.ENVIRONMENT }} # Always evaluates to "deploy-"
|
|
55
|
+
cancel-in-progress: false
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: "Fixed — use github context or vars instead of env"
|
|
58
|
+
code: |
|
|
59
|
+
# ✅ FIXED: use github context properties (available at scheduling time)
|
|
60
|
+
concurrency:
|
|
61
|
+
group: deploy-${{ github.ref_name }}-${{ github.workflow }}
|
|
62
|
+
cancel-in-progress: false
|
|
63
|
+
- language: yaml
|
|
64
|
+
label: "Fixed — pass environment as workflow_dispatch input for dynamic group key"
|
|
65
|
+
code: |
|
|
66
|
+
# ✅ FIXED: expose the value as an input so it's available via `inputs` context
|
|
67
|
+
on:
|
|
68
|
+
workflow_dispatch:
|
|
69
|
+
inputs:
|
|
70
|
+
environment:
|
|
71
|
+
required: true
|
|
72
|
+
type: choice
|
|
73
|
+
options: [production, staging]
|
|
74
|
+
|
|
75
|
+
concurrency:
|
|
76
|
+
group: deploy-${{ inputs.environment }}
|
|
77
|
+
cancel-in-progress: false
|
|
78
|
+
- language: yaml
|
|
79
|
+
label: "Fixed — use repository variable (vars context is available)"
|
|
80
|
+
code: |
|
|
81
|
+
# ✅ FIXED: vars context is available in concurrency expressions
|
|
82
|
+
concurrency:
|
|
83
|
+
group: deploy-${{ vars.DEPLOY_ENV }}-${{ github.ref_name }}
|
|
84
|
+
cancel-in-progress: false
|
|
85
|
+
prevention:
|
|
86
|
+
- "Only use `github`, `inputs`, and `vars` contexts in `concurrency.group` expressions."
|
|
87
|
+
- "If you see runs from unrelated branches cancelling each other, inspect the concurrency group key for empty-string evaluation."
|
|
88
|
+
- "Test concurrency group expressions by adding a step that echoes the group key: `run: echo 'group=${{ github.workflow }}-${{ github.ref_name }}'`."
|
|
89
|
+
- "If concurrency cancellation messages show an empty group name `''`, the expression evaluated to an empty string."
|
|
90
|
+
- "Use `vars` (repository/org variables) rather than `env` when you need a configured value in the group key."
|
|
91
|
+
docs:
|
|
92
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/using-concurrency"
|
|
93
|
+
label: "Using concurrency — supported expression contexts"
|
|
94
|
+
- url: "https://github.com/orgs/community/discussions/26308"
|
|
95
|
+
label: "GitHub Community #26308 — env context not available in concurrency"
|
|
96
|
+
- url: "https://github.com/orgs/community/discussions/69704"
|
|
97
|
+
label: "GitHub Community #69704 — concurrency group context limitations"
|
|
98
|
+
- url: "https://github.com/orgs/community/discussions/45734"
|
|
99
|
+
label: "GitHub Community #45734 — concurrency expression supported contexts"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
id: concurrency-timing-013
|
|
2
|
+
title: "Required Status Check Stuck in Pending When Workflow Skipped by Path or Branch Filter"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- required-status-check
|
|
7
|
+
- path-filter
|
|
8
|
+
- branch-filter
|
|
9
|
+
- pending
|
|
10
|
+
- pull-request
|
|
11
|
+
- branch-protection
|
|
12
|
+
- paths
|
|
13
|
+
- blocked-pr
|
|
14
|
+
patterns:
|
|
15
|
+
- regex: "Some checks haven't completed yet|Required status check.*pending"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "Waiting for status:.*pending"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Some checks haven't completed yet"
|
|
21
|
+
- "Required status check is pending"
|
|
22
|
+
- "Waiting for status: CI / test (pending)"
|
|
23
|
+
root_cause: |
|
|
24
|
+
GitHub Actions workflows that use `paths:`, `paths-ignore:`, `branches:`, or
|
|
25
|
+
`branches-ignore:` filters will NOT run — and will NOT report ANY status — for
|
|
26
|
+
commits that don't match the filter criteria.
|
|
27
|
+
|
|
28
|
+
When a required status check is configured in a branch protection rule and the
|
|
29
|
+
workflow providing that check is skipped by a filter:
|
|
30
|
+
- The check is NEVER created for that commit — it remains in "Pending" state indefinitely
|
|
31
|
+
- The PR is blocked from merging with "Some checks haven't completed yet"
|
|
32
|
+
- The check CANNOT be manually re-triggered without pushing a commit that matches the filter
|
|
33
|
+
|
|
34
|
+
Common scenario:
|
|
35
|
+
A repository has a `ci.yml` workflow with `paths: ['src/**', '*.ts']` and
|
|
36
|
+
`CI / test` configured as a required status check. A developer opens a PR that only
|
|
37
|
+
changes `README.md` or `.github/docs/`. The `CI / test` check never runs, shows as
|
|
38
|
+
"Pending" forever, and the PR is permanently blocked from merging without an admin
|
|
39
|
+
override or a dummy code commit to trigger the workflow.
|
|
40
|
+
|
|
41
|
+
This is explicitly documented behavior but frequently misunderstood:
|
|
42
|
+
- The workflow appears to work correctly for code-change PRs (the common case)
|
|
43
|
+
- The bug only surfaces on documentation-only, config-only, or administrative PRs
|
|
44
|
+
- Developers and reviewers see a pending check with no way to trigger it
|
|
45
|
+
|
|
46
|
+
Note: This is distinct from skipped-needs-cascade (job dependency skipping) — this
|
|
47
|
+
is specifically about the WORKFLOW TRIGGER filter preventing the run from ever starting,
|
|
48
|
+
so no job status is reported at all.
|
|
49
|
+
|
|
50
|
+
Source: GitHub Docs — Troubleshooting required status checks: "If a workflow is skipped
|
|
51
|
+
due to path filtering, branch filtering or a commit message, then checks associated
|
|
52
|
+
with that workflow will remain in a 'Pending' state. A pull request that requires those
|
|
53
|
+
checks to be successful will be blocked from merging."
|
|
54
|
+
fix: |
|
|
55
|
+
Two main approaches:
|
|
56
|
+
|
|
57
|
+
1. **Always-succeeding bypass job** — remove path filters from the workflow trigger,
|
|
58
|
+
run the workflow for all PRs, use `dorny/paths-filter` or `tj-actions/changed-files`
|
|
59
|
+
to detect changes inside the workflow, and add a sentinel job that always produces a
|
|
60
|
+
status. Configure the required check to point at the sentinel job name.
|
|
61
|
+
|
|
62
|
+
2. **Split workflow** — keep the path-filtered workflow for actual CI work, and add a
|
|
63
|
+
separate always-running workflow that provides the required status check name
|
|
64
|
+
(succeeds immediately for non-code PRs, waits for CI for code PRs).
|
|
65
|
+
fix_code:
|
|
66
|
+
- language: yaml
|
|
67
|
+
label: "Fix: always-running workflow with internal path detection and sentinel job"
|
|
68
|
+
code: |
|
|
69
|
+
name: CI
|
|
70
|
+
# No path filter — workflow always runs for all PRs
|
|
71
|
+
on:
|
|
72
|
+
pull_request:
|
|
73
|
+
branches: [main]
|
|
74
|
+
|
|
75
|
+
jobs:
|
|
76
|
+
changes:
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
outputs:
|
|
79
|
+
code: ${{ steps.filter.outputs.code }}
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@v4
|
|
82
|
+
- uses: dorny/paths-filter@v3
|
|
83
|
+
id: filter
|
|
84
|
+
with:
|
|
85
|
+
filters: |
|
|
86
|
+
code:
|
|
87
|
+
- 'src/**'
|
|
88
|
+
- '*.ts'
|
|
89
|
+
- 'package*.json'
|
|
90
|
+
|
|
91
|
+
test:
|
|
92
|
+
needs: changes
|
|
93
|
+
if: needs.changes.outputs.code == 'true'
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
steps:
|
|
96
|
+
- uses: actions/checkout@v4
|
|
97
|
+
- run: npm ci && npm test
|
|
98
|
+
|
|
99
|
+
# Branch protection required check: "CI / ci-gate" (not "CI / test")
|
|
100
|
+
# Always produces a status — green for docs PRs, waits for test on code PRs
|
|
101
|
+
ci-gate:
|
|
102
|
+
needs: [changes, test]
|
|
103
|
+
if: always()
|
|
104
|
+
runs-on: ubuntu-latest
|
|
105
|
+
steps:
|
|
106
|
+
- name: Confirm CI passed or code was not changed
|
|
107
|
+
run: |
|
|
108
|
+
CODE_CHANGED="${{ needs.changes.outputs.code }}"
|
|
109
|
+
TEST_RESULT="${{ needs.test.result }}"
|
|
110
|
+
if [[ "$CODE_CHANGED" == "false" ]]; then
|
|
111
|
+
echo "✅ No code changes — CI gate passes automatically"
|
|
112
|
+
elif [[ "$TEST_RESULT" == "success" ]]; then
|
|
113
|
+
echo "✅ Tests passed"
|
|
114
|
+
else
|
|
115
|
+
echo "❌ Tests $TEST_RESULT"
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
- language: yaml
|
|
120
|
+
label: "Alternative: run all steps always but skip expensive ones via filter"
|
|
121
|
+
code: |
|
|
122
|
+
name: CI
|
|
123
|
+
on:
|
|
124
|
+
pull_request:
|
|
125
|
+
branches: [main]
|
|
126
|
+
# No workflow-level path filter — status always reported
|
|
127
|
+
|
|
128
|
+
jobs:
|
|
129
|
+
test:
|
|
130
|
+
runs-on: ubuntu-latest
|
|
131
|
+
steps:
|
|
132
|
+
- uses: actions/checkout@v4
|
|
133
|
+
|
|
134
|
+
- uses: dorny/paths-filter@v3
|
|
135
|
+
id: filter
|
|
136
|
+
with:
|
|
137
|
+
filters: |
|
|
138
|
+
code: ['src/**', '*.ts', 'package*.json']
|
|
139
|
+
|
|
140
|
+
- name: Install dependencies
|
|
141
|
+
if: steps.filter.outputs.code == 'true'
|
|
142
|
+
run: npm ci
|
|
143
|
+
|
|
144
|
+
- name: Run tests
|
|
145
|
+
if: steps.filter.outputs.code == 'true'
|
|
146
|
+
run: npm test
|
|
147
|
+
# Job always completes with success — check is always reported
|
|
148
|
+
prevention:
|
|
149
|
+
- "Never use workflow-level `paths:` or `branches:` filters as the sole trigger for a required status check."
|
|
150
|
+
- "Use `dorny/paths-filter` or `tj-actions/changed-files` INSIDE an always-running workflow instead of workflow-level path filters."
|
|
151
|
+
- "Name required status checks after jobs that always produce a status, even on non-code PRs."
|
|
152
|
+
- "Test branch protection rules by opening a documentation-only PR to verify all required checks complete."
|
|
153
|
+
- "Consider admin overrides as a last resort, not a workflow fix — the root cause will keep happening."
|
|
154
|
+
docs:
|
|
155
|
+
- url: "https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks"
|
|
156
|
+
label: "Troubleshooting required status checks (path filter skip documented)"
|
|
157
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onpushpull_requestpull_request_targetpathspaths-ignore"
|
|
158
|
+
label: "Workflow syntax: paths and paths-ignore filters"
|
|
159
|
+
- url: "https://github.com/dorny/paths-filter"
|
|
160
|
+
label: "dorny/paths-filter — detect changed files inside workflow"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
id: concurrency-timing-012
|
|
2
|
+
title: "Deployment wait-timer + cancel-in-progress: true Creates Permanent Deployment Starvation Loop"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- wait-timer
|
|
7
|
+
- cancel-in-progress
|
|
8
|
+
- deployment
|
|
9
|
+
- environment
|
|
10
|
+
- starvation
|
|
11
|
+
- concurrency
|
|
12
|
+
- production
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "Run was cancelled|Canceling since a higher priority waiting request"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "wait.?timer|waiting for environment.*approval"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "Run was cancelled"
|
|
20
|
+
- "Canceling since a higher priority waiting request for 'production' exists"
|
|
21
|
+
root_cause: |
|
|
22
|
+
When a deployment workflow combines `concurrency.cancel-in-progress: true` with a
|
|
23
|
+
deployment environment that has a `wait-timer` configured (a mandatory delay before
|
|
24
|
+
deployment proceeds), every new commit to the branch creates a starvation loop where
|
|
25
|
+
no deployment ever reaches the execution phase:
|
|
26
|
+
|
|
27
|
+
1. Run A starts → deployment job begins waiting out the environment wait-timer (e.g., 5 min)
|
|
28
|
+
2. A new commit is pushed → Run B starts in the same concurrency group
|
|
29
|
+
3. `cancel-in-progress: true` fires → Run A is cancelled while still in the wait-timer
|
|
30
|
+
4. Run B now begins its own wait-timer countdown
|
|
31
|
+
5. Another commit arrives → Run B is cancelled during its timer
|
|
32
|
+
6. This repeats indefinitely — no deployment ever executes
|
|
33
|
+
|
|
34
|
+
This loop is particularly insidious because:
|
|
35
|
+
- All cancellations appear expected and benign in the Actions UI (no failures shown)
|
|
36
|
+
- The repository looks healthy — CI passes, deployments start — but production is
|
|
37
|
+
silently never updated
|
|
38
|
+
- Active development repos where commits arrive faster than the wait-timer duration
|
|
39
|
+
are especially vulnerable
|
|
40
|
+
|
|
41
|
+
Note: GitHub's concurrency model allows only ONE pending run per group. With
|
|
42
|
+
`cancel-in-progress: true`, a new run cancels the RUNNING run (not just queues) —
|
|
43
|
+
so even a very short wait-timer cannot escape this if commits arrive frequently.
|
|
44
|
+
fix: |
|
|
45
|
+
Do not combine `cancel-in-progress: true` with deployment environment `wait-timer`
|
|
46
|
+
on the same workflow. Use `cancel-in-progress: false` (the default) for deploy
|
|
47
|
+
workflows — this queues runs so each commit eventually deploys in order.
|
|
48
|
+
|
|
49
|
+
If you want fast feedback for CI but reliable deployments for CD, split them into
|
|
50
|
+
separate workflow files with different concurrency strategies.
|
|
51
|
+
fix_code:
|
|
52
|
+
- language: yaml
|
|
53
|
+
label: "Fix: disable cancel-in-progress for deploy workflow with wait-timer"
|
|
54
|
+
code: |
|
|
55
|
+
name: Deploy to Production
|
|
56
|
+
on:
|
|
57
|
+
push:
|
|
58
|
+
branches: [main]
|
|
59
|
+
|
|
60
|
+
concurrency:
|
|
61
|
+
group: deploy-production
|
|
62
|
+
cancel-in-progress: false # Queue — never starve a deployment with wait-timer
|
|
63
|
+
|
|
64
|
+
jobs:
|
|
65
|
+
deploy:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
environment: production # Has wait-timer: 5 configured
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
- run: ./deploy.sh
|
|
71
|
+
|
|
72
|
+
- language: yaml
|
|
73
|
+
label: "Split pipeline: CI cancels freely; deploy queues safely after CI"
|
|
74
|
+
code: |
|
|
75
|
+
# ci.yml — fast feedback, cancel stale runs is fine
|
|
76
|
+
name: CI
|
|
77
|
+
on:
|
|
78
|
+
push:
|
|
79
|
+
branches: [main]
|
|
80
|
+
concurrency:
|
|
81
|
+
group: ci-${{ github.ref }}
|
|
82
|
+
cancel-in-progress: true # OK: no side effects
|
|
83
|
+
|
|
84
|
+
jobs:
|
|
85
|
+
test:
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@v4
|
|
89
|
+
- run: npm test
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
# deploy.yml — triggered after CI, environment has wait-timer
|
|
93
|
+
name: Deploy
|
|
94
|
+
on:
|
|
95
|
+
workflow_run:
|
|
96
|
+
workflows: ["CI"]
|
|
97
|
+
types: [completed]
|
|
98
|
+
branches: [main]
|
|
99
|
+
|
|
100
|
+
concurrency:
|
|
101
|
+
group: deploy-production
|
|
102
|
+
cancel-in-progress: false # Queue; every successful CI run gets deployed
|
|
103
|
+
|
|
104
|
+
jobs:
|
|
105
|
+
deploy:
|
|
106
|
+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
|
107
|
+
runs-on: ubuntu-latest
|
|
108
|
+
environment: production # wait-timer is safe — no cancel-in-progress racing it
|
|
109
|
+
steps:
|
|
110
|
+
- uses: actions/checkout@v4
|
|
111
|
+
with:
|
|
112
|
+
ref: ${{ github.event.workflow_run.head_sha }}
|
|
113
|
+
- run: ./deploy.sh
|
|
114
|
+
prevention:
|
|
115
|
+
- "Never combine `cancel-in-progress: true` with a deployment environment `wait-timer` in the same workflow."
|
|
116
|
+
- "Use `cancel-in-progress: false` for any workflow that deploys to environments with protection rules."
|
|
117
|
+
- "Decouple CI (cancel-ok, fast) from CD (queued, reliable) into separate workflow files."
|
|
118
|
+
- "Monitor the Actions tab for a pattern of all deployments showing as CANCELLED — this is a sign of starvation."
|
|
119
|
+
docs:
|
|
120
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/using-concurrency"
|
|
121
|
+
label: "GitHub Docs: Using concurrency in GitHub Actions"
|
|
122
|
+
- url: "https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment#wait-timer"
|
|
123
|
+
label: "GitHub Docs: Managing environments — wait timer"
|
|
124
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run"
|
|
125
|
+
label: "workflow_run event — decouple CI and CD pipelines"
|