@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.
Files changed (34) hide show
  1. package/errors/caching-artifacts/cache-corrupt-on-cancel-during-restore-save-always.yml +136 -0
  2. package/errors/caching-artifacts/restore-keys-asterisk-literal-not-glob.yml +107 -0
  3. package/errors/concurrency-timing/concurrency-timing-053.yml +83 -0
  4. package/errors/concurrency-timing/pull-request-review-shared-concurrency-cancels-ci.yml +131 -0
  5. package/errors/known-unsolved/github-script-esm-not-supported.yml +111 -0
  6. package/errors/known-unsolved/job-outputs-string-only-no-array-object.yml +142 -0
  7. package/errors/known-unsolved/known-unsolved-062.yml +87 -0
  8. package/errors/known-unsolved/runner-rest-api-busy-false-broker-state-desync.yml +102 -0
  9. package/errors/permissions-auth/oidc-immutable-sub-claim-new-repo-trust-policy-mismatch.yml +122 -0
  10. package/errors/permissions-auth/permissions-auth-064.yml +122 -0
  11. package/errors/permissions-auth/permissions-auth-065.yml +97 -0
  12. package/errors/permissions-auth/permissions-auth-066.yml +129 -0
  13. package/errors/permissions-auth/upload-code-coverage-missing-code-quality-write-permission.yml +94 -0
  14. package/errors/runner-environment/arc-kubernetes-checkout-circular-json-container-hook.yml +101 -0
  15. package/errors/runner-environment/cache-restore-windows-runner-silent-crash.yml +130 -0
  16. package/errors/runner-environment/git-248-fetch-tags-shallow-clone-regression.yml +100 -0
  17. package/errors/runner-environment/javascript-actions-alpine-arm64-not-supported.yml +121 -0
  18. package/errors/runner-environment/runner-environment-188.yml +96 -0
  19. package/errors/runner-environment/runner-environment-191.yml +147 -0
  20. package/errors/runner-environment/runner-environment-192.yml +144 -0
  21. package/errors/runner-environment/runner-environment-193.yml +136 -0
  22. package/errors/runner-environment/runner-environment-194.yml +86 -0
  23. package/errors/runner-environment/runner-environment-199.yml +93 -0
  24. package/errors/runner-environment/setup-python-macos-self-hosted-symlink-permission-denied.yml +94 -0
  25. package/errors/runner-environment/setup-python-windows-self-hosted-no-admin-install-fails.yml +101 -0
  26. package/errors/silent-failures/checkout-v6-clean-false-deletes-workspace-on-repo-change.yml +119 -0
  27. package/errors/silent-failures/queue-max-silently-ignored-with-cancel-in-progress.yml +109 -0
  28. package/errors/silent-failures/silent-failures-102.yml +141 -0
  29. package/errors/silent-failures/silent-failures-104.yml +119 -0
  30. package/errors/triggers/triggers-069.yml +100 -0
  31. package/errors/yaml-syntax/continue-on-error-inputs-composite-action-unexpected-value.yml +110 -0
  32. package/errors/yaml-syntax/yaml-syntax-068.yml +137 -0
  33. package/errors/yaml-syntax/yaml-syntax-069.yml +118 -0
  34. 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'