@htekdev/actions-debugger 1.0.113 → 1.0.115
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/errors/caching-artifacts/cache-corrupt-on-cancel-during-restore-save-always.yml +136 -0
- package/errors/caching-artifacts/restore-keys-asterisk-literal-not-glob.yml +107 -0
- package/errors/concurrency-timing/concurrency-timing-053.yml +83 -0
- package/errors/concurrency-timing/pull-request-review-shared-concurrency-cancels-ci.yml +131 -0
- package/errors/known-unsolved/github-script-esm-not-supported.yml +111 -0
- package/errors/known-unsolved/job-outputs-string-only-no-array-object.yml +142 -0
- package/errors/known-unsolved/known-unsolved-062.yml +87 -0
- package/errors/known-unsolved/runner-rest-api-busy-false-broker-state-desync.yml +102 -0
- package/errors/permissions-auth/oidc-immutable-sub-claim-new-repo-trust-policy-mismatch.yml +122 -0
- package/errors/permissions-auth/permissions-auth-064.yml +122 -0
- package/errors/permissions-auth/permissions-auth-065.yml +97 -0
- package/errors/permissions-auth/permissions-auth-066.yml +129 -0
- package/errors/permissions-auth/upload-code-coverage-missing-code-quality-write-permission.yml +94 -0
- package/errors/runner-environment/arc-kubernetes-checkout-circular-json-container-hook.yml +101 -0
- package/errors/runner-environment/cache-restore-windows-runner-silent-crash.yml +130 -0
- package/errors/runner-environment/git-248-fetch-tags-shallow-clone-regression.yml +100 -0
- package/errors/runner-environment/javascript-actions-alpine-arm64-not-supported.yml +121 -0
- package/errors/runner-environment/runner-environment-188.yml +96 -0
- package/errors/runner-environment/runner-environment-191.yml +147 -0
- package/errors/runner-environment/runner-environment-192.yml +144 -0
- package/errors/runner-environment/runner-environment-193.yml +136 -0
- package/errors/runner-environment/runner-environment-194.yml +86 -0
- package/errors/runner-environment/runner-environment-199.yml +93 -0
- package/errors/runner-environment/setup-python-macos-self-hosted-symlink-permission-denied.yml +94 -0
- package/errors/runner-environment/setup-python-windows-self-hosted-no-admin-install-fails.yml +101 -0
- package/errors/silent-failures/checkout-v6-clean-false-deletes-workspace-on-repo-change.yml +119 -0
- package/errors/silent-failures/queue-max-silently-ignored-with-cancel-in-progress.yml +109 -0
- package/errors/silent-failures/silent-failures-102.yml +141 -0
- package/errors/silent-failures/silent-failures-104.yml +119 -0
- package/errors/triggers/triggers-069.yml +100 -0
- package/errors/yaml-syntax/continue-on-error-inputs-composite-action-unexpected-value.yml +110 -0
- package/errors/yaml-syntax/yaml-syntax-068.yml +137 -0
- package/errors/yaml-syntax/yaml-syntax-069.yml +118 -0
- package/package.json +1 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
id: caching-artifacts-067
|
|
2
|
+
title: "Cache corrupted when workflow canceled mid-restore and save runs via always()"
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- cancellation
|
|
8
|
+
- corrupt
|
|
9
|
+
- restore
|
|
10
|
+
- always
|
|
11
|
+
- windows
|
|
12
|
+
- tar
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'The operation was canceled'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'internal error.*InstallPlan.*not in configured state'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
- regex: 'Cache restored successfully'
|
|
19
|
+
flags: 'i'
|
|
20
|
+
- regex: 'Cache hit for:.*\nReceived.*\nError: The operation was canceled'
|
|
21
|
+
flags: 'is'
|
|
22
|
+
error_messages:
|
|
23
|
+
- "Error: The operation was canceled."
|
|
24
|
+
- "Cache restored successfully"
|
|
25
|
+
- "internal error in InstallPlan.completed: not in configured state: Installed"
|
|
26
|
+
root_cause: |
|
|
27
|
+
When a workflow is canceled while `actions/cache/restore` is mid-way through
|
|
28
|
+
extracting the cache archive (via `tar`), the extraction is interrupted and the
|
|
29
|
+
working directory is left in a **partially extracted, corrupt state**. The cache
|
|
30
|
+
hit was registered before extraction began, so the action considers the key
|
|
31
|
+
matched.
|
|
32
|
+
|
|
33
|
+
If a separate `actions/cache/save` step runs with `if: always()` (or the job has
|
|
34
|
+
`save-state` / explicit save at the end), it executes despite the cancellation.
|
|
35
|
+
It then compresses and saves the **partial working directory** — including the
|
|
36
|
+
incomplete files from the aborted tar extraction — under the exact same cache
|
|
37
|
+
key.
|
|
38
|
+
|
|
39
|
+
On subsequent workflow runs:
|
|
40
|
+
1. `actions/cache/restore` finds the key (exact match), downloads and extracts
|
|
41
|
+
the archive successfully ("Cache restored successfully").
|
|
42
|
+
2. The build starts with corrupt or incomplete files from the previous partial
|
|
43
|
+
extraction and fails with cryptic internal errors, not a caching error.
|
|
44
|
+
|
|
45
|
+
The symptom is particularly confusing because the restore step looks perfectly
|
|
46
|
+
healthy: it shows 100% download progress and "Cache restored successfully", but
|
|
47
|
+
the build then fails with errors like "not in configured state: Installed" (Cabal
|
|
48
|
+
Haskell), "invalid content", "module not found" (npm), or similar tool-specific
|
|
49
|
+
corruption errors.
|
|
50
|
+
|
|
51
|
+
This is distinct from `caching-artifacts-007` (cache post-step skipped on cancel
|
|
52
|
+
— where the cache is NOT saved). This entry covers the opposite scenario: an
|
|
53
|
+
explicit `always()` save step actively commits a corrupt partial state, poisoning
|
|
54
|
+
the cache for all future runs until the key expires or is manually deleted.
|
|
55
|
+
fix: |
|
|
56
|
+
1. **Delete the corrupt cache entry** via the GitHub Actions UI (Settings →
|
|
57
|
+
Actions → Caches) or the GitHub API:
|
|
58
|
+
`DELETE /repos/{owner}/{repo}/actions/caches/{cache_id}`
|
|
59
|
+
|
|
60
|
+
2. **Guard the save step** so it only runs when restoration AND the build
|
|
61
|
+
succeeded — not unconditionally:
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
- name: Save cache
|
|
65
|
+
uses: actions/cache/save@v4
|
|
66
|
+
if: success() && steps.restore.outputs.cache-hit != 'true'
|
|
67
|
+
with:
|
|
68
|
+
path: ...
|
|
69
|
+
key: ...
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
3. **Avoid pairing `always()` with cache save** when the prior restore step
|
|
73
|
+
may have been interrupted. Use explicit `success()` or a combination
|
|
74
|
+
`if: success() || steps.restore.conclusion == 'success'`.
|
|
75
|
+
|
|
76
|
+
4. On Windows, consider using `actions/cache@v4` with `enableCrossOsArchive: false`
|
|
77
|
+
and switching to `zstd`-backed archives, which are more resilient to partial
|
|
78
|
+
extraction (partial frames are detectable by zstd checksums).
|
|
79
|
+
fix_code:
|
|
80
|
+
- language: yaml
|
|
81
|
+
label: "WRONG — always() save can commit partial corrupt state after cancellation"
|
|
82
|
+
code: |
|
|
83
|
+
- name: Restore cache
|
|
84
|
+
id: restore
|
|
85
|
+
uses: actions/cache/restore@v4
|
|
86
|
+
with:
|
|
87
|
+
path: ~/.cabal/store
|
|
88
|
+
key: cabal-${{ runner.os }}-${{ hashFiles('cabal.project.freeze') }}
|
|
89
|
+
|
|
90
|
+
# ... build steps ...
|
|
91
|
+
|
|
92
|
+
- name: Save cache
|
|
93
|
+
uses: actions/cache/save@v4
|
|
94
|
+
if: always() # ← PROBLEM: runs even if restore was interrupted
|
|
95
|
+
with:
|
|
96
|
+
path: ~/.cabal/store
|
|
97
|
+
key: cabal-${{ runner.os }}-${{ hashFiles('cabal.project.freeze') }}
|
|
98
|
+
- language: yaml
|
|
99
|
+
label: "CORRECT — only save when restore and build succeeded"
|
|
100
|
+
code: |
|
|
101
|
+
- name: Restore cache
|
|
102
|
+
id: restore
|
|
103
|
+
uses: actions/cache/restore@v4
|
|
104
|
+
with:
|
|
105
|
+
path: ~/.cabal/store
|
|
106
|
+
key: cabal-${{ runner.os }}-${{ hashFiles('cabal.project.freeze') }}
|
|
107
|
+
restore-keys: |
|
|
108
|
+
cabal-${{ runner.os }}-
|
|
109
|
+
|
|
110
|
+
# ... build steps ...
|
|
111
|
+
|
|
112
|
+
- name: Save cache
|
|
113
|
+
uses: actions/cache/save@v4
|
|
114
|
+
if: success() # ← SAFE: only runs on clean job success
|
|
115
|
+
with:
|
|
116
|
+
path: ~/.cabal/store
|
|
117
|
+
key: cabal-${{ runner.os }}-${{ hashFiles('cabal.project.freeze') }}
|
|
118
|
+
prevention:
|
|
119
|
+
- "Never use `if: always()` on a `cache/save` step — use `if: success()` or
|
|
120
|
+
an explicit condition that confirms the build completed cleanly."
|
|
121
|
+
- "After any job cancellation that hit a cache restore step, check the GitHub
|
|
122
|
+
Actions Caches list and delete the affected cache key to prevent future runs
|
|
123
|
+
from inheriting the corrupt state."
|
|
124
|
+
- "Use `actions/cache@v4` in the combined save-on-miss pattern with the default
|
|
125
|
+
post-job hook only (no explicit save step); the post-job hook is automatically
|
|
126
|
+
skipped on cancellation, avoiding the corrupt save."
|
|
127
|
+
- "Consider setting a short TTL on Windows caches or using unique keys per run
|
|
128
|
+
(`key: ...-${{ github.run_id }}`) for artifacts that are sensitive to partial
|
|
129
|
+
extraction."
|
|
130
|
+
docs:
|
|
131
|
+
- url: "https://github.com/actions/cache/issues/1729"
|
|
132
|
+
label: "actions/cache#1729: If a workflow is canceled during cache/restore then cache/save writes a corrupt cache (Windows)"
|
|
133
|
+
- url: "https://github.com/actions/toolkit/tree/main/packages/cache"
|
|
134
|
+
label: "@actions/cache package documentation"
|
|
135
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows"
|
|
136
|
+
label: "Caching dependencies to speed up workflows"
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
id: caching-artifacts-066
|
|
2
|
+
title: 'restore-keys patterns are literal prefix strings — asterisk is not a glob wildcard'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- actions/cache
|
|
8
|
+
- restore-keys
|
|
9
|
+
- prefix
|
|
10
|
+
- cache-miss
|
|
11
|
+
- glob
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'restore-keys:.*\*'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'Cache not found for input keys:'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'No cache found for key'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- 'Cache not found for input keys:'
|
|
21
|
+
- 'No cache found for key'
|
|
22
|
+
- '(silent cache miss — no error, restore-keys with asterisk simply fails to match any existing cache entry)'
|
|
23
|
+
root_cause: |
|
|
24
|
+
GitHub Actions cache restore-keys are prefix strings, not glob patterns. The
|
|
25
|
+
asterisk (*) character has no special meaning — it is treated as a literal
|
|
26
|
+
character in the prefix match.
|
|
27
|
+
|
|
28
|
+
A restore-keys entry of:
|
|
29
|
+
restore-keys: npm-${{ runner.os }}-*
|
|
30
|
+
|
|
31
|
+
does NOT match cache keys like:
|
|
32
|
+
npm-Ubuntu-abc123hash
|
|
33
|
+
npm-Ubuntu-lockfile-abcdef
|
|
34
|
+
|
|
35
|
+
It only matches keys whose name starts with the literal string
|
|
36
|
+
"npm-Ubuntu-*" (including the asterisk), which virtually never exist,
|
|
37
|
+
resulting in a silent cache miss with no error message.
|
|
38
|
+
|
|
39
|
+
This mistake is widespread because:
|
|
40
|
+
1. The actions/cache documentation uses the term "prefix" but does not
|
|
41
|
+
explicitly state that glob characters are unsupported or treated literally.
|
|
42
|
+
2. Many other caching tools (Gradle, Maven, Buildkite, CircleCI) accept glob
|
|
43
|
+
or wildcard patterns in cache key configurations, leading developers to
|
|
44
|
+
apply the same syntax to GitHub Actions.
|
|
45
|
+
3. The failure mode is silent — the step reports "Cache not found for input keys"
|
|
46
|
+
and continues normally. Without inspecting the exact key string printed in the
|
|
47
|
+
step log, the asterisk in the pattern is invisible as the cause.
|
|
48
|
+
|
|
49
|
+
The correct behavior: restore-keys prefix matching finds any cache entry whose
|
|
50
|
+
key BEGINS WITH the given string. Trailing a prefix with a dash or separator
|
|
51
|
+
(e.g., npm-Ubuntu-) is all that is needed to match any entry starting with
|
|
52
|
+
that prefix — no asterisk required or supported.
|
|
53
|
+
fix: |
|
|
54
|
+
Remove any glob characters from restore-keys entries. A bare prefix string
|
|
55
|
+
ending in a dash or other separator correctly matches all cache entries whose
|
|
56
|
+
key starts with that prefix:
|
|
57
|
+
|
|
58
|
+
restore-keys: |
|
|
59
|
+
npm-${{ runner.os }}- ← correct: prefix without asterisk
|
|
60
|
+
|
|
61
|
+
The cache service automatically returns the most recently saved matching entry,
|
|
62
|
+
so multiple ordered restore-keys fallback entries can be stacked from most
|
|
63
|
+
specific to broadest.
|
|
64
|
+
fix_code:
|
|
65
|
+
- language: yaml
|
|
66
|
+
label: 'Broken: asterisk treated as literal — no cached keys match this pattern'
|
|
67
|
+
code: |
|
|
68
|
+
- uses: actions/cache@v4
|
|
69
|
+
with:
|
|
70
|
+
path: ~/.npm
|
|
71
|
+
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
|
72
|
+
restore-keys: |
|
|
73
|
+
npm-${{ runner.os }}-*
|
|
74
|
+
- language: yaml
|
|
75
|
+
label: 'Fixed: bare prefix (no asterisk) correctly matches all prior cache entries'
|
|
76
|
+
code: |
|
|
77
|
+
- uses: actions/cache@v4
|
|
78
|
+
with:
|
|
79
|
+
path: ~/.npm
|
|
80
|
+
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
|
81
|
+
restore-keys: |
|
|
82
|
+
npm-${{ runner.os }}-
|
|
83
|
+
- language: yaml
|
|
84
|
+
label: 'Multiple fallback tiers from most specific to broadest (no globs needed)'
|
|
85
|
+
code: |
|
|
86
|
+
- uses: actions/cache@v4
|
|
87
|
+
with:
|
|
88
|
+
path: |
|
|
89
|
+
~/.npm
|
|
90
|
+
~/.cache/Cypress
|
|
91
|
+
key: node-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
|
92
|
+
restore-keys: |
|
|
93
|
+
node-${{ runner.os }}-${{ matrix.node-version }}-
|
|
94
|
+
node-${{ runner.os }}-
|
|
95
|
+
node-
|
|
96
|
+
prevention:
|
|
97
|
+
- 'Never use glob characters (*, ?, []) in restore-keys — they are literal string characters; the cache key service does not interpret them as wildcards'
|
|
98
|
+
- 'End restore-keys prefix entries with a trailing dash or separator: restore-keys: npm-${{ runner.os }}- not npm-${{ runner.os }}-*'
|
|
99
|
+
- 'If cache misses persist despite restore-keys being configured, add a debug step that echoes the exact key and restore-keys strings being evaluated (using set -x or explicit echo) to spot literal asterisks in the output'
|
|
100
|
+
- 'Do not confuse restore-keys with glob support in path: — the path: field DOES support glob patterns; restore-keys does not'
|
|
101
|
+
docs:
|
|
102
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#matching-a-cache-key'
|
|
103
|
+
label: 'GitHub Docs: Matching a cache key'
|
|
104
|
+
- url: 'https://github.com/actions/cache#inputs'
|
|
105
|
+
label: 'actions/cache README: Inputs — restore-keys'
|
|
106
|
+
- url: 'https://github.com/actions/cache/blob/main/tips-and-workarounds.md'
|
|
107
|
+
label: 'actions/cache: Tips and workarounds'
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
id: concurrency-timing-053
|
|
2
|
+
title: 'Concurrency pending slot overflow: third concurrent run silently cancels the already-queued second run when cancel-in-progress is false'
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- concurrency
|
|
7
|
+
- cancel-in-progress
|
|
8
|
+
- pending
|
|
9
|
+
- queue
|
|
10
|
+
- silent-cancellation
|
|
11
|
+
- overflow
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Canceling since a higher priority waiting run was found'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'This run was automatically cancelled'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- 'Canceling since a higher priority waiting run was found'
|
|
19
|
+
- 'This run was automatically cancelled'
|
|
20
|
+
root_cause: |
|
|
21
|
+
GitHub Actions concurrency groups with cancel-in-progress: false allow at most one
|
|
22
|
+
run to be in-progress and one run to be pending (queued) simultaneously per group.
|
|
23
|
+
This queue depth is exactly 1, not unlimited.
|
|
24
|
+
|
|
25
|
+
When a third run arrives while run 1 is in-progress and run 2 is pending:
|
|
26
|
+
- Run 2 (the pending run) is immediately and silently cancelled
|
|
27
|
+
- Run 3 takes run 2's pending slot
|
|
28
|
+
|
|
29
|
+
The user who triggered run 2 typically sees it flip from "Queued" to "Cancelled"
|
|
30
|
+
with no notification and no failure alert. From their perspective their commit's CI
|
|
31
|
+
simply disappeared.
|
|
32
|
+
|
|
33
|
+
This behavior is documented in GitHub docs but surprises teams that expect a FIFO
|
|
34
|
+
queue of unlimited depth. The concurrency feature is a mutex with a single
|
|
35
|
+
waiting-room slot, not a job scheduler queue.
|
|
36
|
+
|
|
37
|
+
Common scenarios where this causes silent data loss:
|
|
38
|
+
- Rapid-fire merges to a protected branch with slow integration tests
|
|
39
|
+
- Multiple developers pushing within seconds of each other to the same branch
|
|
40
|
+
- Automated commits (dependency updates, release bots) arriving while CI runs
|
|
41
|
+
fix: |
|
|
42
|
+
Option 1 — Accept the overflow: intended behavior for fast-merge scenarios where only
|
|
43
|
+
the LATEST commit needs CI. No change needed.
|
|
44
|
+
|
|
45
|
+
Option 2 — Widen the concurrency key so each commit gets its own slot:
|
|
46
|
+
group: ${{ github.workflow }}-${{ github.sha }}
|
|
47
|
+
This disables cancellation entirely; every run completes regardless of newer pushes.
|
|
48
|
+
|
|
49
|
+
Option 3 — Use cancel-in-progress: true explicitly if "latest wins" is the desired
|
|
50
|
+
semantics. In-progress runs cancel rather than queued runs disappearing silently.
|
|
51
|
+
|
|
52
|
+
Option 4 — Queue at the runner group level by using a self-hosted runner group with
|
|
53
|
+
a concurrency limit to provide true multi-run queuing.
|
|
54
|
+
fix_code:
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: 'Common mistake: expecting cancel-in-progress: false to queue all pending runs indefinitely'
|
|
57
|
+
code: |
|
|
58
|
+
concurrency:
|
|
59
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
60
|
+
cancel-in-progress: false
|
|
61
|
+
# Only 1 run can be pending; a 3rd arriving run silently cancels the queued 2nd
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: 'Option A: per-commit group key — every run completes, no cancellation at all'
|
|
64
|
+
code: |
|
|
65
|
+
concurrency:
|
|
66
|
+
group: ${{ github.workflow }}-${{ github.sha }}
|
|
67
|
+
cancel-in-progress: false
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: 'Option B: cancel-in-progress: true — explicit latest-wins, in-progress runs cancelled not pending ones'
|
|
70
|
+
code: |
|
|
71
|
+
concurrency:
|
|
72
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
73
|
+
cancel-in-progress: true
|
|
74
|
+
prevention:
|
|
75
|
+
- 'Understand that cancel-in-progress: false does not create an unlimited queue — it allows exactly one pending run per concurrency group key'
|
|
76
|
+
- 'For deployment workflows where no commit should be skipped, use per-commit group keys (${{ github.sha }}) to guarantee every run completes'
|
|
77
|
+
- 'Monitor the Actions tab during rapid-push periods to verify queued runs are completing, not silently disappearing'
|
|
78
|
+
- 'Prefer cancel-in-progress: true when only the latest result matters; the cancellation is explicit and visible rather than silent'
|
|
79
|
+
docs:
|
|
80
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-concurrency'
|
|
81
|
+
label: 'GitHub Docs: Using concurrency'
|
|
82
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-concurrency#example-only-cancel-in-progress-jobs-or-runs-for-the-current-workflow'
|
|
83
|
+
label: 'GitHub Docs: Concurrency — one pending slot per group'
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
id: concurrency-timing-052
|
|
2
|
+
title: 'pull_request_review event in shared concurrency group cancels in-progress CI on reviewer submission'
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- concurrency
|
|
7
|
+
- pull_request_review
|
|
8
|
+
- pull_request
|
|
9
|
+
- cancel-in-progress
|
|
10
|
+
- code-review
|
|
11
|
+
- ci-cancellation
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'pull_request_review'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'Canceling since a higher priority waiting run was found'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'This run was automatically cancelled'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
error_messages:
|
|
20
|
+
- 'This run was automatically cancelled'
|
|
21
|
+
- 'Canceling since a higher priority waiting run was found'
|
|
22
|
+
root_cause: |
|
|
23
|
+
When a workflow responds to both on.pull_request and on.pull_request_review, and
|
|
24
|
+
uses a concurrency group keyed on github.event.pull_request.number, both event
|
|
25
|
+
types resolve to the same concurrency group key for a given PR.
|
|
26
|
+
|
|
27
|
+
Unlike push events where github.event.pull_request is absent, the
|
|
28
|
+
pull_request_review event payload DOES include a pull_request object with a number
|
|
29
|
+
property — so github.event.pull_request.number evaluates identically for both
|
|
30
|
+
pull_request [synchronize] and pull_request_review [submitted] events on the same PR.
|
|
31
|
+
|
|
32
|
+
As a result, every reviewer action — approvals, review comments, change requests,
|
|
33
|
+
and re-requests — triggers a new workflow run that shares the same concurrency slot
|
|
34
|
+
as the CI run from the last code push. With cancel-in-progress: true, a reviewer
|
|
35
|
+
merely submitting an approval silently cancels the running CI checks for that PR,
|
|
36
|
+
causing the PR to show pending or missing status checks despite no new commit being
|
|
37
|
+
pushed.
|
|
38
|
+
|
|
39
|
+
This catches teams off-guard because the reviewer action is unrelated to the code
|
|
40
|
+
under test — no new commit arrived — yet the running CI is terminated. The PR
|
|
41
|
+
may be left with required checks in a "Pending" state even though nothing has
|
|
42
|
+
changed, and developers must manually re-run CI to restore a passing state.
|
|
43
|
+
fix: |
|
|
44
|
+
Include github.event_name in the concurrency group key so that pull_request CI
|
|
45
|
+
runs and pull_request_review-triggered runs occupy separate, independent slots:
|
|
46
|
+
|
|
47
|
+
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number }}
|
|
48
|
+
|
|
49
|
+
Alternatively, split the pull_request_review trigger into a separate workflow file
|
|
50
|
+
that handles review-gated tasks (labeling, auto-approve, notifications) while
|
|
51
|
+
keeping CI in a dedicated pull_request-only workflow with its own concurrency group.
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: 'Broken: reviewer approval cancels in-progress CI run'
|
|
55
|
+
code: |
|
|
56
|
+
on:
|
|
57
|
+
pull_request:
|
|
58
|
+
pull_request_review:
|
|
59
|
+
types: [submitted]
|
|
60
|
+
|
|
61
|
+
concurrency:
|
|
62
|
+
group: ci-${{ github.event.pull_request.number }}
|
|
63
|
+
cancel-in-progress: true
|
|
64
|
+
|
|
65
|
+
jobs:
|
|
66
|
+
test:
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
- run: make test
|
|
71
|
+
- language: yaml
|
|
72
|
+
label: 'Fixed: include event_name to give each event type its own concurrency slot'
|
|
73
|
+
code: |
|
|
74
|
+
on:
|
|
75
|
+
pull_request:
|
|
76
|
+
pull_request_review:
|
|
77
|
+
types: [submitted]
|
|
78
|
+
|
|
79
|
+
concurrency:
|
|
80
|
+
# pull_request pushes: ci-pull_request-42
|
|
81
|
+
# pull_request_review submissions: ci-pull_request_review-42 (independent)
|
|
82
|
+
group: ci-${{ github.event_name }}-${{ github.event.pull_request.number }}
|
|
83
|
+
cancel-in-progress: true
|
|
84
|
+
|
|
85
|
+
jobs:
|
|
86
|
+
test:
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
steps:
|
|
89
|
+
- uses: actions/checkout@v4
|
|
90
|
+
- run: make test
|
|
91
|
+
- language: yaml
|
|
92
|
+
label: 'Alternative: split CI and review automation into separate workflow files'
|
|
93
|
+
code: |
|
|
94
|
+
# .github/workflows/ci.yml — triggered by code changes only
|
|
95
|
+
on:
|
|
96
|
+
pull_request:
|
|
97
|
+
|
|
98
|
+
concurrency:
|
|
99
|
+
group: ci-${{ github.event.pull_request.number }}
|
|
100
|
+
cancel-in-progress: true
|
|
101
|
+
|
|
102
|
+
jobs:
|
|
103
|
+
test:
|
|
104
|
+
runs-on: ubuntu-latest
|
|
105
|
+
steps:
|
|
106
|
+
- uses: actions/checkout@v4
|
|
107
|
+
- run: make test
|
|
108
|
+
|
|
109
|
+
# .github/workflows/review-automation.yml — review gating only
|
|
110
|
+
on:
|
|
111
|
+
pull_request_review:
|
|
112
|
+
types: [submitted]
|
|
113
|
+
|
|
114
|
+
jobs:
|
|
115
|
+
label-approved:
|
|
116
|
+
if: github.event.review.state == 'approved'
|
|
117
|
+
runs-on: ubuntu-latest
|
|
118
|
+
steps:
|
|
119
|
+
- run: echo "PR approved — add label or trigger deploy"
|
|
120
|
+
prevention:
|
|
121
|
+
- 'Audit any workflow with multiple on: event triggers for cross-event concurrency group collisions — confirm that each event type resolves to a unique group key unless intentional sharing is desired'
|
|
122
|
+
- 'Include ${{ github.event_name }} in concurrency group keys for workflows triggered by more than one event type to prevent unintended cross-event cancellation'
|
|
123
|
+
- 'Use separate workflow files for CI checks (on.pull_request) and review-automation tasks (on.pull_request_review) to avoid shared concurrency group interference'
|
|
124
|
+
- 'When debugging unexpectedly cancelled CI runs, check the Actions tab for runs with "Cancelled" status — filter by trigger event to identify whether a pull_request_review submission was the cancelling run'
|
|
125
|
+
docs:
|
|
126
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_review'
|
|
127
|
+
label: 'GitHub Docs: pull_request_review event'
|
|
128
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-concurrency'
|
|
129
|
+
label: 'GitHub Docs: Using concurrency'
|
|
130
|
+
- url: 'https://github.com/orgs/community/discussions/26336'
|
|
131
|
+
label: 'GitHub Community: Concurrency cancel-in-progress cancels CI on review submission'
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
id: known-unsolved-061
|
|
2
|
+
title: 'actions/github-script does not support ESM — import statements and ESM-only packages fail'
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- github-script
|
|
7
|
+
- esm
|
|
8
|
+
- commonjs
|
|
9
|
+
- import
|
|
10
|
+
- ERR_REQUIRE_ESM
|
|
11
|
+
- octokit
|
|
12
|
+
- javascript
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'SyntaxError: Cannot use import statement in a module'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'ERR_REQUIRE_ESM'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
- regex: 'require\(\) of ES Module.*is not supported'
|
|
19
|
+
flags: 'i'
|
|
20
|
+
- regex: 'must use import to load ES Module'
|
|
21
|
+
flags: 'i'
|
|
22
|
+
error_messages:
|
|
23
|
+
- "SyntaxError: Cannot use import statement in a module"
|
|
24
|
+
- "Error [ERR_REQUIRE_ESM]: require() of ES Module /path/to/node_modules/pkg/index.mjs is not supported."
|
|
25
|
+
- "Instead change the require of ... to a dynamic import() which is available in all CommonJS modules."
|
|
26
|
+
- "Error: Must use import to load ES Module: /path/to/node_modules/pkg/index.js"
|
|
27
|
+
root_cause: |
|
|
28
|
+
actions/github-script evaluates the inline `script:` input using Node.js vm.Script in a
|
|
29
|
+
CommonJS (CJS) module context. ECMAScript Module (ESM) syntax is fundamentally incompatible
|
|
30
|
+
with this execution model:
|
|
31
|
+
|
|
32
|
+
1. Top-level `import ... from` statements → SyntaxError: "Cannot use import statement in a module"
|
|
33
|
+
because vm.Script runs in CJS mode and the `import` keyword is a syntax error at parse time.
|
|
34
|
+
|
|
35
|
+
2. require() of an ESM-only package (e.g., modern @octokit/rest ≥ 5.x, chalk ≥ 5.x,
|
|
36
|
+
node-fetch ≥ 3.x, got ≥ 12.x, axios ≥ 2.x) → ERR_REQUIRE_ESM because those packages
|
|
37
|
+
publish only .mjs or set "type": "module" in package.json, making them incompatible
|
|
38
|
+
with require().
|
|
39
|
+
|
|
40
|
+
3. External scripts loaded via `const fn = require('./my-script.js')` also run in a CJS
|
|
41
|
+
context — ESM syntax inside those files also fails.
|
|
42
|
+
|
|
43
|
+
The github-script action has no current plan to migrate to ESM execution, and supporting
|
|
44
|
+
both modes simultaneously in vm.Script is architecturally complex. This is a long-standing
|
|
45
|
+
limitation open since 2024 with 30+ reactions and no scheduled resolution.
|
|
46
|
+
|
|
47
|
+
The GitHub Actions toolkit itself (github-script's own dependencies) also pins to
|
|
48
|
+
CJS-compatible versions of @octokit packages for this reason.
|
|
49
|
+
fix: |
|
|
50
|
+
There is no built-in fix — actions/github-script does not support ESM. Use these workarounds:
|
|
51
|
+
|
|
52
|
+
WORKAROUND 1 (recommended): Use dynamic import() inside an async function
|
|
53
|
+
Dynamic import() IS supported inside github-script because it is a CJS-compatible
|
|
54
|
+
expression (not top-level ESM syntax). Wrap the entire script in an async IIFE or
|
|
55
|
+
use the top-level await that github-script already provides:
|
|
56
|
+
|
|
57
|
+
const { Octokit } = await import('@octokit/rest')
|
|
58
|
+
|
|
59
|
+
WORKAROUND 2: Use a CJS-compatible version of the package
|
|
60
|
+
Many packages (chalk, got, axios, node-fetch) have a last CJS-compatible version.
|
|
61
|
+
Pin to that in your workflow step using node or npm:
|
|
62
|
+
|
|
63
|
+
chalk: ^4.1.2 (last CJS version; chalk 5.x is ESM-only)
|
|
64
|
+
node-fetch: ^2.7.0 (last CJS version; 3.x is ESM-only)
|
|
65
|
+
got: ^11.8.6 (last CJS version; 12.x is ESM-only)
|
|
66
|
+
|
|
67
|
+
WORKAROUND 3: Create a composite or JavaScript action with a proper ESM Node runtime
|
|
68
|
+
For complex scripts that heavily use ESM packages, extract them into a standalone
|
|
69
|
+
composite or JavaScript action that declares "main": "./dist/index.mjs" in action.yml.
|
|
70
|
+
fix_code:
|
|
71
|
+
- language: yaml
|
|
72
|
+
label: 'Use dynamic import() for ESM-only packages in github-script'
|
|
73
|
+
code: |
|
|
74
|
+
- name: Use ESM package via dynamic import()
|
|
75
|
+
uses: actions/github-script@v7
|
|
76
|
+
with:
|
|
77
|
+
script: |
|
|
78
|
+
// Dynamic import() works in github-script even though top-level import does not
|
|
79
|
+
const { Octokit } = await import('@octokit/rest')
|
|
80
|
+
const { default: chalk } = await import('chalk')
|
|
81
|
+
// github, context, core are still available as usual
|
|
82
|
+
const result = await github.rest.repos.get({
|
|
83
|
+
owner: context.repo.owner,
|
|
84
|
+
repo: context.repo.repo,
|
|
85
|
+
})
|
|
86
|
+
core.info(chalk.green(`Repo: ${result.data.full_name}`))
|
|
87
|
+
- language: yaml
|
|
88
|
+
label: 'Pin to last CJS-compatible version of common packages'
|
|
89
|
+
code: |
|
|
90
|
+
- name: Install CJS-compatible package versions
|
|
91
|
+
run: npm install chalk@4 node-fetch@2 got@11
|
|
92
|
+
|
|
93
|
+
- name: Use CJS packages in github-script
|
|
94
|
+
uses: actions/github-script@v7
|
|
95
|
+
with:
|
|
96
|
+
script: |
|
|
97
|
+
const chalk = require('chalk') // chalk@4 is CJS — works fine
|
|
98
|
+
const fetch = require('node-fetch') // node-fetch@2 is CJS
|
|
99
|
+
core.info(chalk.blue('Using CJS-compatible packages'))
|
|
100
|
+
prevention:
|
|
101
|
+
- 'Check whether a package is ESM-only before using it in github-script — look for "type": "module" in its package.json'
|
|
102
|
+
- 'Use dynamic import() as the default pattern for any external package in github-script'
|
|
103
|
+
- 'For scripts that require heavy ESM ecosystem usage, consider a dedicated JavaScript action instead of github-script'
|
|
104
|
+
- 'Stay on chalk@4, node-fetch@2, got@11, axios@1 in github-script workflows — these are the last CJS-compatible majors'
|
|
105
|
+
docs:
|
|
106
|
+
- url: 'https://github.com/actions/github-script/issues/457'
|
|
107
|
+
label: 'actions/github-script#457: Feature request — ESM support (open, 30+ reactions)'
|
|
108
|
+
- url: 'https://nodejs.org/api/esm.html#interoperability-with-commonjs'
|
|
109
|
+
label: 'Node.js Docs — ESM and CommonJS interoperability'
|
|
110
|
+
- url: 'https://github.com/actions/github-script#use-scripts-with-jsdoc-support'
|
|
111
|
+
label: 'actions/github-script — README: use scripts with JSDoc support'
|