@htekdev/actions-debugger 1.0.14 → 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.
Files changed (50) hide show
  1. package/dist/db/search.js +3 -1
  2. package/dist/db/search.js.map +1 -1
  3. package/dist/tools/suggest-fix.d.ts.map +1 -1
  4. package/dist/tools/suggest-fix.js +5 -1
  5. package/dist/tools/suggest-fix.js.map +1 -1
  6. package/errors/caching-artifacts/cache-key-too-long.yml +93 -0
  7. package/errors/caching-artifacts/cache-path-not-exist-skipped.yml +152 -0
  8. package/errors/caching-artifacts/docker-buildx-gha-cache-capacity.yml +107 -0
  9. package/errors/caching-artifacts/setup-ruby-bundler-ephemeral-workdir-cache-miss.yml +147 -0
  10. package/errors/caching-artifacts/upload-artifact-v3-retirement-blocked.yml +123 -0
  11. package/errors/concurrency-timing/always-cleanup-5min-forced-kill.yml +140 -0
  12. package/errors/concurrency-timing/concurrency-group-env-context-undefined.yml +99 -0
  13. package/errors/concurrency-timing/required-check-pending-path-filter-skip.yml +160 -0
  14. package/errors/concurrency-timing/wait-timer-cancel-in-progress-starvation.yml +125 -0
  15. package/errors/known-unsolved/composite-action-step-timeout-minutes-ignored.yml +146 -0
  16. package/errors/known-unsolved/reusable-workflow-no-composite-action-call.yml +116 -0
  17. package/errors/known-unsolved/schedule-trigger-default-branch-only.yml +113 -0
  18. package/errors/known-unsolved/secrets-not-allowed-in-if-conditions.yml +149 -0
  19. package/errors/permissions-auth/dependabot-pr-secrets-unavailable.yml +133 -0
  20. package/errors/permissions-auth/fine-grained-pat-deployment-write-required.yml +146 -0
  21. package/errors/permissions-auth/github-app-installation-token-new-format.yml +124 -0
  22. package/errors/permissions-auth/github-packages-read-requires-packages-permission.yml +128 -0
  23. package/errors/permissions-auth/oidc-id-token-write-permission-missing.yml +169 -0
  24. package/errors/permissions-auth/permissions-empty-block-removes-contents-read.yml +97 -0
  25. package/errors/permissions-auth/reusable-workflow-permissions-not-inherited.yml +114 -0
  26. package/errors/runner-environment/checkout-windows-ebusy-lock.yml +124 -0
  27. package/errors/runner-environment/deprecated-action-version-auto-rejected.yml +89 -0
  28. package/errors/runner-environment/github-hosted-runner-disk-space-full.yml +85 -0
  29. package/errors/runner-environment/github-path-same-step-not-found.yml +114 -0
  30. package/errors/runner-environment/github-script-v6-octokit-rest-actions-not-function.yml +87 -0
  31. package/errors/runner-environment/macos-15-mono-nuget-removed.yml +151 -0
  32. package/errors/runner-environment/macos-15-xcode-simulator-sdk-policy.yml +141 -0
  33. package/errors/runner-environment/runner-oom-exit-code-137.yml +117 -0
  34. package/errors/runner-environment/setup-go-go123-telemetry-cache-failure.yml +92 -0
  35. package/errors/runner-environment/setup-java-distribution-required.yml +108 -0
  36. package/errors/runner-environment/windows-latest-d-drive-removed.yml +104 -0
  37. package/errors/runner-environment/windows-vs2026-cuda-host-compiler-unsupported.yml +145 -0
  38. package/errors/silent-failures/event-commits-empty-on-workflow-dispatch.yml +110 -0
  39. package/errors/silent-failures/fetch-tags-depth-one-silent-no-op.yml +77 -0
  40. package/errors/silent-failures/github-env-multiline-value-truncated.yml +127 -0
  41. package/errors/silent-failures/github-sha-pr-merge-commit-not-head.yml +150 -0
  42. package/errors/silent-failures/job-output-masked-as-secret-empty.yml +147 -0
  43. package/errors/silent-failures/upload-artifact-permissions-stripped.yml +98 -0
  44. package/errors/triggers/pull-request-branches-filter-matches-base-not-head.yml +140 -0
  45. package/errors/triggers/push-event-fires-on-branch-delete.yml +129 -0
  46. package/errors/triggers/push-first-commit-before-sha-zeros.yml +160 -0
  47. package/errors/yaml-syntax/fromjson-empty-string-crash.yml +99 -0
  48. package/errors/yaml-syntax/if-bang-negation-yaml-tag.yml +145 -0
  49. package/errors/yaml-syntax/local-action-path-always-top-level.yml +142 -0
  50. 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"