@htekdev/actions-debugger 1.0.121 → 1.0.123
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/caching-artifacts-072.yml +137 -0
- package/errors/runner-environment/runner-environment-221.yml +145 -0
- package/errors/runner-environment/runner-environment-222.yml +140 -0
- package/errors/runner-environment/runner-environment-223.yml +149 -0
- package/errors/triggers/triggers-071.yml +117 -0
- package/errors/yaml-syntax/yaml-syntax-074.yml +130 -0
- package/package.json +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
id: caching-artifacts-072
|
|
2
|
+
title: 'actions/cache@v5 Restore Rate Limit (429) Silently Treated as Cache Miss'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- rate-limit
|
|
8
|
+
- 429
|
|
9
|
+
- restore
|
|
10
|
+
- cache-miss
|
|
11
|
+
- silent-failure
|
|
12
|
+
- v5
|
|
13
|
+
- performance
|
|
14
|
+
patterns:
|
|
15
|
+
- regex: 'Warning: You''ve hit a rate limit, your rate limit will reset in \d+ seconds'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'Failed to restore:.*GetCacheEntryDownloadURL.*Rate [Ll]imited'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
- regex: 'Failed request: \(429\) Too Many Requests: rate limit exceeded'
|
|
20
|
+
flags: 'i'
|
|
21
|
+
error_messages:
|
|
22
|
+
- "Warning: You've hit a rate limit, your rate limit will reset in 18 seconds"
|
|
23
|
+
- "Warning: Failed to restore: Failed to GetCacheEntryDownloadURL: Rate Limited: Failed request: (429) Too Many Requests: rate limit exceeded"
|
|
24
|
+
- "Cache not found for input keys: goofy-b41b01ad3312fe1358359b7522c43860bfdad754166c7f1d385e51766e57b4c0"
|
|
25
|
+
root_cause: |
|
|
26
|
+
When the GitHub cache service rate-limits a cache restore lookup request with HTTP
|
|
27
|
+
429 Too Many Requests, actions/cache@v5 does NOT retry the request. Instead, it
|
|
28
|
+
prints a warning and immediately treats the response as a cache miss, proceeding
|
|
29
|
+
with a full build from scratch.
|
|
30
|
+
|
|
31
|
+
The cache service includes a Retry-After header in the 429 response that tells the
|
|
32
|
+
client exactly how many seconds to wait before retrying (often ≤60 seconds). The
|
|
33
|
+
actions/cache implementation ignores this header entirely — no retry, no backoff,
|
|
34
|
+
no configurable behavior. The job simply never gets its cached dependencies.
|
|
35
|
+
|
|
36
|
+
This is a silent failure in the sense that:
|
|
37
|
+
1. The job succeeds — it just rebuilds everything from scratch.
|
|
38
|
+
2. No annotation or error is surfaced in the Actions UI. Only a Warning line in
|
|
39
|
+
the step log reveals what happened.
|
|
40
|
+
3. The resulting build artifacts are correct, but the CI run takes 2–10x longer
|
|
41
|
+
than expected, masking the real cause.
|
|
42
|
+
|
|
43
|
+
Most commonly triggered in large matrix builds (20+ parallel jobs) where many jobs
|
|
44
|
+
simultaneously query the cache service and collectively exhaust the per-repo or
|
|
45
|
+
per-org cache API rate limit. Also reported on repos with heavy cross-job cache
|
|
46
|
+
sharing patterns.
|
|
47
|
+
|
|
48
|
+
Distinct from caching-artifacts-030 (cache-service-429-upload-ebadf-crash.yml):
|
|
49
|
+
that entry covers 429 during the cache UPLOAD phase which crashes with EBADF.
|
|
50
|
+
This entry covers 429 during the cache RESTORE/lookup phase which silently misses
|
|
51
|
+
— different operation, different error message, different impact, different fix path.
|
|
52
|
+
|
|
53
|
+
Source: actions/cache#1758 (May 2026, open); also reported in
|
|
54
|
+
oxidecomputer/hubris#2535 "CI fails intermittently on Windows while restoring cache"
|
|
55
|
+
(May 2026).
|
|
56
|
+
fix: |
|
|
57
|
+
There is no complete fix — this is an open upstream bug (actions/cache#1758).
|
|
58
|
+
The rate-limit retry path is not implemented in actions/cache. Workarounds:
|
|
59
|
+
|
|
60
|
+
Option 1 — Reduce cache API pressure by staggering matrix jobs:
|
|
61
|
+
|
|
62
|
+
strategy:
|
|
63
|
+
matrix: ...
|
|
64
|
+
max-parallel: 5 # Limit to 5 concurrent jobs instead of all at once
|
|
65
|
+
|
|
66
|
+
This reduces the burst of simultaneous restore calls and lowers the chance of
|
|
67
|
+
hitting the rate limit.
|
|
68
|
+
|
|
69
|
+
Option 2 — Add a retry wrapper using actions/cache's restore-keys cascade:
|
|
70
|
+
|
|
71
|
+
- uses: actions/cache@v5
|
|
72
|
+
id: cache
|
|
73
|
+
with:
|
|
74
|
+
key: ${{ runner.os }}-deps-${{ hashFiles('**/lockfile') }}
|
|
75
|
+
restore-keys: |
|
|
76
|
+
${{ runner.os }}-deps-
|
|
77
|
+
- name: Warn on cache rate limit miss
|
|
78
|
+
if: steps.cache.outputs.cache-hit != 'true'
|
|
79
|
+
run: |
|
|
80
|
+
echo "::warning::Cache miss — may be rate limited. Check step log for 429."
|
|
81
|
+
|
|
82
|
+
Option 3 — Switch to a self-hosted cache backend to bypass GitHub's rate limits:
|
|
83
|
+
|
|
84
|
+
env:
|
|
85
|
+
ACTIONS_CACHE_URL: https://your-cache-backend.example.com/
|
|
86
|
+
ACTIONS_RUNTIME_TOKEN: ${{ secrets.CACHE_TOKEN }}
|
|
87
|
+
|
|
88
|
+
Option 4 — Accept it and add monitoring. If you frequently see the rate limit
|
|
89
|
+
warning, consider filing a support ticket to request a higher cache API rate limit
|
|
90
|
+
for your organization.
|
|
91
|
+
fix_code:
|
|
92
|
+
- language: yaml
|
|
93
|
+
label: 'Reduce parallelism to lower cache restore burst pressure'
|
|
94
|
+
code: |
|
|
95
|
+
jobs:
|
|
96
|
+
build:
|
|
97
|
+
strategy:
|
|
98
|
+
matrix:
|
|
99
|
+
target: [linux-x64, linux-arm64, windows-x64, macos-x64, macos-arm64]
|
|
100
|
+
max-parallel: 4 # Stagger jobs to reduce simultaneous cache restore calls
|
|
101
|
+
steps:
|
|
102
|
+
- uses: actions/cache@v5
|
|
103
|
+
with:
|
|
104
|
+
path: ~/.cargo/registry
|
|
105
|
+
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
106
|
+
|
|
107
|
+
- language: yaml
|
|
108
|
+
label: 'Add explicit warning step to surface rate limit cache misses clearly'
|
|
109
|
+
code: |
|
|
110
|
+
steps:
|
|
111
|
+
- uses: actions/cache@v5
|
|
112
|
+
id: cache-restore
|
|
113
|
+
with:
|
|
114
|
+
path: |
|
|
115
|
+
~/.npm
|
|
116
|
+
node_modules
|
|
117
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
118
|
+
restore-keys: |
|
|
119
|
+
${{ runner.os }}-node-
|
|
120
|
+
|
|
121
|
+
- name: Check for cache rate limit miss
|
|
122
|
+
if: steps.cache-restore.outputs.cache-hit != 'true'
|
|
123
|
+
run: |
|
|
124
|
+
echo "::warning::Cache miss detected — check step log for '429 Too Many Requests' to distinguish rate-limit miss from genuine cache absence."
|
|
125
|
+
|
|
126
|
+
prevention:
|
|
127
|
+
- 'Set max-parallel on matrix strategies to limit simultaneous cache restore API calls and avoid triggering the per-org/per-repo cache rate limit.'
|
|
128
|
+
- 'Monitor the cache restore step logs for the warning message "You''ve hit a rate limit" to distinguish rate-limit misses from genuine cache absences when diagnosing slow CI runs.'
|
|
129
|
+
- 'Consider using restore-keys fallback chains so that even a rate-limited primary key miss may still succeed with a partial restore from a broader key.'
|
|
130
|
+
- 'Report rate limit occurrences to GitHub Support with your org name to request a higher cache API rate limit if you encounter this regularly in large workflows.'
|
|
131
|
+
docs:
|
|
132
|
+
- url: 'https://github.com/actions/cache/issues/1758'
|
|
133
|
+
label: 'actions/cache#1758 — Handle rate limit (open, May 2026)'
|
|
134
|
+
- url: 'https://github.com/oxidecomputer/hubris/issues/2535'
|
|
135
|
+
label: 'oxidecomputer/hubris#2535 — CI fails intermittently on Windows while restoring cache (May 2026)'
|
|
136
|
+
- url: 'https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy'
|
|
137
|
+
label: 'GitHub Docs — Caching usage limits and eviction policy'
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
id: runner-environment-221
|
|
2
|
+
title: 'actions/checkout@v6 Hangs at git-credential-osxkeychain on macOS Self-Hosted Runners with Concurrent Jobs'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- checkout
|
|
7
|
+
- macos
|
|
8
|
+
- osxkeychain
|
|
9
|
+
- credential-helper
|
|
10
|
+
- deadlock
|
|
11
|
+
- self-hosted
|
|
12
|
+
- concurrent
|
|
13
|
+
- v6
|
|
14
|
+
- hang
|
|
15
|
+
patterns:
|
|
16
|
+
- regex: 'trace: start_command:.*git-credential-osxkeychain store'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
- regex: 'trace: run_command: .?git credential-osxkeychain store.?$'
|
|
19
|
+
flags: 'im'
|
|
20
|
+
- regex: 'credential-osxkeychain store.*\n.*\n.*\n.*\n.*checkout.*hang'
|
|
21
|
+
flags: 'im'
|
|
22
|
+
error_messages:
|
|
23
|
+
- "trace: run_command: 'git credential-osxkeychain store'"
|
|
24
|
+
- "trace: start_command: /bin/sh -c 'git credential-osxkeychain store' 'git credential-osxkeychain store'"
|
|
25
|
+
- "trace: exec: git-credential-osxkeychain store"
|
|
26
|
+
- "trace: start_command: /opt/homebrew/opt/git/libexec/git-core/git-credential-osxkeychain store"
|
|
27
|
+
root_cause: |
|
|
28
|
+
On macOS self-hosted runners, Git uses git-credential-osxkeychain as the default
|
|
29
|
+
credential helper. When actions/checkout@v6 runs with persist-credentials: true
|
|
30
|
+
(the default), it stores the GITHUB_TOKEN in the macOS Keychain via the osxkeychain
|
|
31
|
+
credential helper.
|
|
32
|
+
|
|
33
|
+
The macOS Keychain grants exclusive write locks to one process at a time. When two or
|
|
34
|
+
more jobs run actions/checkout@v6 concurrently on the same self-hosted runner machine,
|
|
35
|
+
both jobs attempt to call `git credential-osxkeychain store` simultaneously. One process
|
|
36
|
+
acquires the Keychain lock and proceeds; the other blocks indefinitely waiting for the
|
|
37
|
+
lock to be released — which never happens because the macOS Keychain's IPC mechanism
|
|
38
|
+
can deadlock under concurrent access from multiple git processes sharing the same runner
|
|
39
|
+
session.
|
|
40
|
+
|
|
41
|
+
The hung checkout step produces no error output — the last visible log lines are the
|
|
42
|
+
`git-credential-osxkeychain store` trace entries. The job appears to be running but
|
|
43
|
+
makes no progress. Without a step-level timeout, GitHub's 6-hour job timeout eventually
|
|
44
|
+
cancels it.
|
|
45
|
+
|
|
46
|
+
Affected environment:
|
|
47
|
+
- actions/checkout@v6 (v6.0.x, the version that changed credential handling)
|
|
48
|
+
- macOS self-hosted runners, including macOS 26 Tahoe (runner 2.331.0+)
|
|
49
|
+
- Reproduced when ≥2 jobs on the same runner machine execute checkout concurrently
|
|
50
|
+
- Not specific to runner 2.331.0 — also reported on earlier macOS self-hosted setups
|
|
51
|
+
since checkout@v2, but became more frequent with v6's credential handling changes
|
|
52
|
+
|
|
53
|
+
Distinct from runner-environment-032 (persist-credentials: false breaks subsequent
|
|
54
|
+
git push auth — the opposite direction: fixing the push but needing credentials).
|
|
55
|
+
fix: |
|
|
56
|
+
Two workarounds — try Option 1 first, fall back to Option 2 if the deadlock persists:
|
|
57
|
+
|
|
58
|
+
Option 1 — Disable credential persistence for checkout (avoids Keychain writes):
|
|
59
|
+
|
|
60
|
+
- uses: actions/checkout@v6
|
|
61
|
+
with:
|
|
62
|
+
persist-credentials: false
|
|
63
|
+
|
|
64
|
+
This prevents checkout from calling `git credential-osxkeychain store` entirely,
|
|
65
|
+
eliminating the deadlock. Note: if your workflow's later steps need to push changes
|
|
66
|
+
to the repo using git directly (not via GH_TOKEN env var), you must pass the token
|
|
67
|
+
explicitly in the remote URL or use a separate authentication step.
|
|
68
|
+
|
|
69
|
+
Option 2 — Clean workspace before checkout (forces clean lock state):
|
|
70
|
+
|
|
71
|
+
- name: Clean workspace before checkout
|
|
72
|
+
run: |
|
|
73
|
+
find "$GITHUB_WORKSPACE" -mindepth 1 -maxdepth 1 -exec rm -rf {} + \
|
|
74
|
+
|| echo "::warning::Workspace cleanup had warnings (non-fatal)"
|
|
75
|
+
- uses: actions/checkout@v6
|
|
76
|
+
|
|
77
|
+
This removes any pre-existing files that might be holding Git process locks
|
|
78
|
+
from a previous job, allowing checkout to complete cleanly. This is an uglier
|
|
79
|
+
workaround but more effective when persist-credentials: false alone does not help.
|
|
80
|
+
|
|
81
|
+
Option 3 — Disable the macOS credential helper globally for CI git operations:
|
|
82
|
+
|
|
83
|
+
- name: Disable osxkeychain credential helper for CI
|
|
84
|
+
run: git config --global credential.helper ''
|
|
85
|
+
- uses: actions/checkout@v6
|
|
86
|
+
|
|
87
|
+
For git push steps that use an explicit token URL, also set GIT_TERMINAL_PROMPT=0:
|
|
88
|
+
|
|
89
|
+
- name: Push changes
|
|
90
|
+
env:
|
|
91
|
+
GIT_TERMINAL_PROMPT: '0'
|
|
92
|
+
run: |
|
|
93
|
+
git -c credential.helper='' push --force \
|
|
94
|
+
"https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" \
|
|
95
|
+
HEAD:gh-pages
|
|
96
|
+
fix_code:
|
|
97
|
+
- language: yaml
|
|
98
|
+
label: 'Fix 1 — persist-credentials: false prevents Keychain write (preferred)'
|
|
99
|
+
code: |
|
|
100
|
+
steps:
|
|
101
|
+
- uses: actions/checkout@v6
|
|
102
|
+
with:
|
|
103
|
+
persist-credentials: false # Avoids git-credential-osxkeychain store call
|
|
104
|
+
|
|
105
|
+
- language: yaml
|
|
106
|
+
label: 'Fix 2 — clean workspace before checkout to resolve concurrent lock conflicts'
|
|
107
|
+
code: |
|
|
108
|
+
steps:
|
|
109
|
+
- name: Clean workspace before checkout
|
|
110
|
+
run: |
|
|
111
|
+
find "$GITHUB_WORKSPACE" -mindepth 1 -maxdepth 1 -exec rm -rf {} + \
|
|
112
|
+
|| echo "::warning::Cleanup warnings are non-fatal"
|
|
113
|
+
|
|
114
|
+
- uses: actions/checkout@v6
|
|
115
|
+
|
|
116
|
+
- language: yaml
|
|
117
|
+
label: 'Fix 3 — disable osxkeychain globally and use explicit token for git push'
|
|
118
|
+
code: |
|
|
119
|
+
steps:
|
|
120
|
+
- name: Disable macOS keychain credential helper for CI
|
|
121
|
+
run: git config --global credential.helper ''
|
|
122
|
+
|
|
123
|
+
- uses: actions/checkout@v6
|
|
124
|
+
|
|
125
|
+
# Later, for git push steps:
|
|
126
|
+
- name: Push to gh-pages
|
|
127
|
+
env:
|
|
128
|
+
GIT_TERMINAL_PROMPT: '0'
|
|
129
|
+
run: |
|
|
130
|
+
git -c credential.helper='' push --force \
|
|
131
|
+
"https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" \
|
|
132
|
+
HEAD:gh-pages
|
|
133
|
+
|
|
134
|
+
prevention:
|
|
135
|
+
- 'Always set persist-credentials: false on actions/checkout@v6 for macOS self-hosted runners if your jobs do not need subsequent git operations using the GITHUB_TOKEN credential helper.'
|
|
136
|
+
- 'Add a timeout-minutes: on checkout steps on macOS self-hosted runners to bound hang duration (e.g., timeout-minutes: 5) rather than waiting for the 6-hour job timeout.'
|
|
137
|
+
- 'Serialize concurrent jobs on the same macOS runner using a concurrency group, or ensure jobs that checkout concurrently run on different runner instances.'
|
|
138
|
+
- 'Set GIT_TERMINAL_PROMPT=0 in macOS self-hosted runner environments to prevent git from waiting for interactive input from any credential helper.'
|
|
139
|
+
docs:
|
|
140
|
+
- url: 'https://github.com/actions/checkout/issues/550'
|
|
141
|
+
label: 'actions/checkout#550 — Actions checkout gets stuck forever randomly (open, 2021–2026)'
|
|
142
|
+
- url: 'https://stackoverflow.com/questions/79881327/github-actions-self-hosted-runner-on-macos-tries-to-checkout-repository-forever'
|
|
143
|
+
label: 'SO q/79881327 — Github Actions self hosted runner on macOS tries to checkout repository forever (Feb 2026)'
|
|
144
|
+
- url: 'https://github.com/actions/checkout#usage'
|
|
145
|
+
label: 'actions/checkout README — persist-credentials input documentation'
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
id: runner-environment-222
|
|
2
|
+
title: 'Windows Self-Hosted Runner V2 Broker Listener Stops Polling After First Job Completion'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- self-hosted
|
|
7
|
+
- windows
|
|
8
|
+
- broker
|
|
9
|
+
- v2-flow
|
|
10
|
+
- listener
|
|
11
|
+
- polling
|
|
12
|
+
- idle
|
|
13
|
+
- 2.334.0
|
|
14
|
+
- BrokerMessageListener
|
|
15
|
+
patterns:
|
|
16
|
+
- regex: 'BrokerMessageListener.*Get messages has been cancelled using local token source\. Continue to get messages with new status\.'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
- regex: 'BrokerMessageListener.*Received job status event\. JobState: Online'
|
|
19
|
+
flags: 'i'
|
|
20
|
+
error_messages:
|
|
21
|
+
- "[2026-05-21 14:48:13Z INFO BrokerMessageListener] Get messages has been cancelled using local token source. Continue to get messages with new status."
|
|
22
|
+
- "[INFO BrokerMessageListener] Received job status event. JobState: Online"
|
|
23
|
+
- "[INFO BrokerMessageListener] Session created."
|
|
24
|
+
root_cause: |
|
|
25
|
+
On Windows self-hosted runners using the V2 broker protocol (useV2Flow: true,
|
|
26
|
+
serverUrlV2: broker.actions.githubusercontent.com), a race condition in the
|
|
27
|
+
BrokerMessageListener causes the runner to permanently stop polling the broker
|
|
28
|
+
after the first job completes.
|
|
29
|
+
|
|
30
|
+
The sequence that triggers the hang:
|
|
31
|
+
1. Runner starts, creates a broker session, and begins polling for messages.
|
|
32
|
+
2. First job arrives → BrokerMessageListener logs "JobState: Busy".
|
|
33
|
+
3. Job finishes → BrokerMessageListener logs "JobState: Online".
|
|
34
|
+
4. The Online state transition triggers a cancellation of the current polling
|
|
35
|
+
loop via a local token source ("Get messages has been cancelled using
|
|
36
|
+
local token source. Continue to get messages with new status.").
|
|
37
|
+
5. The listener is supposed to create a new polling loop with fresh state, but
|
|
38
|
+
due to a bug in the V2 flow state machine, the new polling loop is never
|
|
39
|
+
started. No further GET /message requests are ever issued.
|
|
40
|
+
|
|
41
|
+
The runner process stays alive. OAuth token refreshes continue on schedule (so
|
|
42
|
+
credentials are not the problem). The runner shows as "Idle" in the GitHub UI.
|
|
43
|
+
However, it will never pick up another job until the service is manually restarted.
|
|
44
|
+
|
|
45
|
+
The bug was introduced in or around v2.334.0 on Windows. It does not affect:
|
|
46
|
+
- Linux runners (different socket layer — see broker-server-socket-exception-nat-timeout-linux.yml)
|
|
47
|
+
- macOS runners (see macos-self-hosted-listener-aad-ghost-busy-stall.yml for a
|
|
48
|
+
separate macOS stall pattern)
|
|
49
|
+
- V1 flow runners (useV2Flow: false)
|
|
50
|
+
- GitHub-hosted runners (not affected by self-hosted listener bugs)
|
|
51
|
+
|
|
52
|
+
Source: actions/runner#4444 (May 2026, open). Reported on Windows Server 2022
|
|
53
|
+
x64, v2.334.0, V2 flow. Three reproducible occurrences in 22 hours on a
|
|
54
|
+
previously stable 6+ day continuous runner.
|
|
55
|
+
fix: |
|
|
56
|
+
Immediate fix — Restart the runner service to recover:
|
|
57
|
+
|
|
58
|
+
Restart-Service actions.runner.*
|
|
59
|
+
|
|
60
|
+
Or via the runner management interface:
|
|
61
|
+
1. Go to repo/org Settings → Actions → Runners
|
|
62
|
+
2. Force-remove the stale runner registration
|
|
63
|
+
3. Re-register and restart
|
|
64
|
+
|
|
65
|
+
Structural workarounds:
|
|
66
|
+
|
|
67
|
+
Option 1 — Switch to ephemeral runners (recommended for most use cases):
|
|
68
|
+
Ephemeral runners register once, run one job, and exit cleanly. No stale state.
|
|
69
|
+
|
|
70
|
+
./config.sh --url https://github.com/ORG/REPO --token TOKEN --ephemeral
|
|
71
|
+
./run.sh
|
|
72
|
+
|
|
73
|
+
Or with Actions Runner Controller (ARC):
|
|
74
|
+
autoscaling.runnerScaleSetListener.minRunners: 1
|
|
75
|
+
|
|
76
|
+
Option 2 — Revert to V1 broker flow if ephemeral is not an option:
|
|
77
|
+
The V1 flow (long-polling, non-broker) does not exhibit this specific hang.
|
|
78
|
+
Edit the .runner config file and set useV2Flow: false, then restart the service.
|
|
79
|
+
Note: V1 is deprecated and will eventually be removed.
|
|
80
|
+
|
|
81
|
+
Option 3 — Add an automatic service recovery watchdog:
|
|
82
|
+
|
|
83
|
+
# Windows Task Scheduler: check every 5 minutes if runner has been Idle >20min
|
|
84
|
+
# and restart the service if it's stuck
|
|
85
|
+
$runner = Get-Service "actions.runner.*"
|
|
86
|
+
if ($runner.Status -eq "Running") {
|
|
87
|
+
# Check last job timestamp via API; if >20min and jobs queued, restart
|
|
88
|
+
Restart-Service "actions.runner.*"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Option 4 — Pin to a known-good runner version:
|
|
92
|
+
If v2.334.0+ reliably triggers this, pin the runner to v2.333.x by editing
|
|
93
|
+
the .runner config and disabling auto-update. Note: outdated versions
|
|
94
|
+
eventually stop being able to receive messages.
|
|
95
|
+
fix_code:
|
|
96
|
+
- language: yaml
|
|
97
|
+
label: 'Use ephemeral runners to avoid stale listener state entirely'
|
|
98
|
+
code: |
|
|
99
|
+
# In your workflow:
|
|
100
|
+
jobs:
|
|
101
|
+
build:
|
|
102
|
+
runs-on: [self-hosted, windows-x64] # Labels for your runner pool
|
|
103
|
+
|
|
104
|
+
# Register runners as ephemeral:
|
|
105
|
+
# ./config.cmd --url https://github.com/ORG/REPO --token TOKEN --ephemeral
|
|
106
|
+
# Each runner exits after completing one job; a process manager (NSSM, task
|
|
107
|
+
# scheduler, or ARC) restarts it to accept the next job.
|
|
108
|
+
|
|
109
|
+
- language: yaml
|
|
110
|
+
label: 'Add watchdog step to detect stale listener symptom (queue depth check)'
|
|
111
|
+
code: |
|
|
112
|
+
# Optional diagnostic: surface "no runners picked up job for >N minutes" via API
|
|
113
|
+
# Run this in a separate monitoring workflow:
|
|
114
|
+
jobs:
|
|
115
|
+
watchdog:
|
|
116
|
+
runs-on: ubuntu-latest
|
|
117
|
+
steps:
|
|
118
|
+
- name: Check for stuck self-hosted Windows runners
|
|
119
|
+
env:
|
|
120
|
+
GH_TOKEN: ${{ secrets.RUNNER_ADMIN_PAT }}
|
|
121
|
+
run: |
|
|
122
|
+
# List queued jobs older than 10 minutes that are assigned to self-hosted
|
|
123
|
+
gh api repos/${{ github.repository }}/actions/runs \
|
|
124
|
+
--jq '.workflow_runs[] | select(.status=="queued") | .id' \
|
|
125
|
+
| while read run_id; do
|
|
126
|
+
echo "Queued run: $run_id"
|
|
127
|
+
done
|
|
128
|
+
|
|
129
|
+
prevention:
|
|
130
|
+
- 'Use ephemeral self-hosted runners — they register, run one job, and exit. No stale listener state can accumulate.'
|
|
131
|
+
- 'If using long-lived runners on Windows with V2 broker flow, add monitoring to detect runners stuck in the Idle state with queued jobs.'
|
|
132
|
+
- 'Set up automatic service recovery for the runner service on Windows (e.g., via Windows Service recovery actions: restart after 1st failure).'
|
|
133
|
+
- 'Monitor actions/runner release notes for a fix to the V2 listener polling regression introduced around v2.334.0.'
|
|
134
|
+
docs:
|
|
135
|
+
- url: 'https://github.com/actions/runner/issues/4444'
|
|
136
|
+
label: 'actions/runner#4444 — Listener stops polling broker after first job''s Busy→Online transition (2.334.0, Windows, V2 flow)'
|
|
137
|
+
- url: 'https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/running-scripts-before-or-after-a-job'
|
|
138
|
+
label: 'GitHub Docs — Self-hosted runner configuration'
|
|
139
|
+
- url: 'https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/autoscaling-with-self-hosted-runners'
|
|
140
|
+
label: 'GitHub Docs — Autoscaling with self-hosted runners (ephemeral runner pattern)'
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
id: runner-environment-223
|
|
2
|
+
title: 'macOS-15 Arm64 brew update Fails with Stale lockf Lock When Run Twice'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- macos
|
|
7
|
+
- homebrew
|
|
8
|
+
- brew-update
|
|
9
|
+
- lockf
|
|
10
|
+
- arm64
|
|
11
|
+
- macos-15
|
|
12
|
+
- regression
|
|
13
|
+
- concurrent
|
|
14
|
+
patterns:
|
|
15
|
+
- regex: 'lockf: 200: already locked'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'Error: Another `brew update` process is already running\.'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
- regex: 'lockf:.*already locked\s*\nError: Another.*brew update.*process is already running'
|
|
20
|
+
flags: 'im'
|
|
21
|
+
error_messages:
|
|
22
|
+
- "lockf: 200: already locked"
|
|
23
|
+
- "Error: Another `brew update` process is already running."
|
|
24
|
+
- "Please wait for it to finish or terminate it to continue."
|
|
25
|
+
- "Error: Process completed with exit code 1."
|
|
26
|
+
root_cause: |
|
|
27
|
+
On macOS-15 Arm64 GitHub Actions hosted runners starting with image version
|
|
28
|
+
20260422.526 (released ~April 22, 2026), running `brew update` more than once
|
|
29
|
+
within the same workflow — or across two steps that both call `brew update` —
|
|
30
|
+
fails on the second invocation with a stale lockf lock error.
|
|
31
|
+
|
|
32
|
+
Homebrew uses a lockfile at `/opt/homebrew/Library/Taps/homebrew/homebrew-core/.git/index.lock`
|
|
33
|
+
(or a similar path) to prevent concurrent updates. In the affected image versions,
|
|
34
|
+
the first `brew update` completes successfully but leaves the lock file in a
|
|
35
|
+
state that subsequent `brew update` calls cannot acquire. The `lockf` system
|
|
36
|
+
call returns errno 200 (EDEADLK on macOS), which Homebrew surfaces as
|
|
37
|
+
"Another brew update process is already running."
|
|
38
|
+
|
|
39
|
+
This is a regression — the same workflow step pattern worked correctly on image
|
|
40
|
+
version 20260415.520 and earlier.
|
|
41
|
+
|
|
42
|
+
Common trigger patterns:
|
|
43
|
+
1. Explicit double-update in a single step: `brew update && brew update`
|
|
44
|
+
2. Two separate steps that each call `brew update` before installing different tools
|
|
45
|
+
3. Parallel jobs on the same runner image that both run `brew update` (less common
|
|
46
|
+
since each hosted runner job gets a fresh VM, but affects matrix jobs in the
|
|
47
|
+
same workflow when they share a Homebrew setup step via the action cache)
|
|
48
|
+
4. A step script that calls `brew update` internally AND the user also calls it
|
|
49
|
+
|
|
50
|
+
Note: This affects macOS-15 Arm64 specifically. macOS-14, macOS-26, and x86_64
|
|
51
|
+
variants were NOT marked as affected in the original bug report (runner-images#13965).
|
|
52
|
+
|
|
53
|
+
Source: actions/runner-images#13965 (April 2026, open, under investigation by
|
|
54
|
+
GitHub runner-images team). Reported with reproducible case from the
|
|
55
|
+
mullvad/mullvadvpn-app CI pipeline.
|
|
56
|
+
fix: |
|
|
57
|
+
Option 1 — Run brew update only once per job (preferred):
|
|
58
|
+
|
|
59
|
+
Consolidate all your brew installations into a single step and call brew update
|
|
60
|
+
exactly once before them:
|
|
61
|
+
|
|
62
|
+
- name: Install dependencies
|
|
63
|
+
run: |
|
|
64
|
+
brew update
|
|
65
|
+
brew install cmake ninja pkg-config
|
|
66
|
+
|
|
67
|
+
Option 2 — Use HOMEBREW_NO_AUTO_UPDATE=1 on steps that don't need fresh formulae:
|
|
68
|
+
|
|
69
|
+
If you only need brew update for specific steps, set the env var on all other
|
|
70
|
+
brew-using steps to prevent automatic update attempts:
|
|
71
|
+
|
|
72
|
+
- name: Install specific tool
|
|
73
|
+
env:
|
|
74
|
+
HOMEBREW_NO_AUTO_UPDATE: '1'
|
|
75
|
+
run: brew install your-tool # Skips the implicit brew update
|
|
76
|
+
|
|
77
|
+
Option 3 — Guard the second brew update with a lock check:
|
|
78
|
+
|
|
79
|
+
- name: Safe brew update
|
|
80
|
+
run: |
|
|
81
|
+
flock -xn /opt/homebrew/Library/Taps/homebrew/homebrew-core/.git/index.lock \
|
|
82
|
+
brew update || echo "::warning::brew update skipped (lock already held)"
|
|
83
|
+
|
|
84
|
+
Option 4 — Use brew upgrade instead of repeated brew update:
|
|
85
|
+
If you need the latest formula versions, run brew update once and then
|
|
86
|
+
use brew upgrade to update installed packages:
|
|
87
|
+
|
|
88
|
+
- name: Update and upgrade Homebrew
|
|
89
|
+
run: |
|
|
90
|
+
brew update # Run exactly once
|
|
91
|
+
brew upgrade # Upgrades installed formulae to latest
|
|
92
|
+
|
|
93
|
+
Option 5 — Check for the regression in your image version and pin:
|
|
94
|
+
If you need to pin to a known-good image version while the fix is pending,
|
|
95
|
+
see GitHub's runner-images documentation for image version pinning options
|
|
96
|
+
(note: pinning is not officially supported for GitHub-hosted standard runners).
|
|
97
|
+
fix_code:
|
|
98
|
+
- language: yaml
|
|
99
|
+
label: 'Broken — two brew update calls in same workflow (second fails on affected image)'
|
|
100
|
+
code: |
|
|
101
|
+
# This fails on macOS-15 Arm64 image 20260422.526+ with lockf: 200: already locked:
|
|
102
|
+
steps:
|
|
103
|
+
- name: Install build tools
|
|
104
|
+
run: |
|
|
105
|
+
brew update
|
|
106
|
+
brew install cmake ninja
|
|
107
|
+
|
|
108
|
+
- name: Install test tools
|
|
109
|
+
run: |
|
|
110
|
+
brew update # FAILS: Another brew update process is already running
|
|
111
|
+
brew install lcov
|
|
112
|
+
|
|
113
|
+
- language: yaml
|
|
114
|
+
label: 'Fixed — single brew update before consolidated installs'
|
|
115
|
+
code: |
|
|
116
|
+
# Consolidate into one brew update call at the start:
|
|
117
|
+
steps:
|
|
118
|
+
- name: Install all Homebrew tools
|
|
119
|
+
run: |
|
|
120
|
+
brew update # Only call once per job
|
|
121
|
+
brew install cmake ninja lcov
|
|
122
|
+
|
|
123
|
+
- language: yaml
|
|
124
|
+
label: 'Fixed — use HOMEBREW_NO_AUTO_UPDATE=1 on subsequent brew steps'
|
|
125
|
+
code: |
|
|
126
|
+
# Or prevent auto-update on steps after the first:
|
|
127
|
+
steps:
|
|
128
|
+
- name: Install build tools
|
|
129
|
+
run: |
|
|
130
|
+
brew update
|
|
131
|
+
brew install cmake ninja
|
|
132
|
+
|
|
133
|
+
- name: Install test tools (no re-update needed)
|
|
134
|
+
env:
|
|
135
|
+
HOMEBREW_NO_AUTO_UPDATE: '1'
|
|
136
|
+
run: brew install lcov # Uses existing formula cache; no brew update call
|
|
137
|
+
|
|
138
|
+
prevention:
|
|
139
|
+
- 'Call brew update at most once per job. Consolidate all Homebrew installations into a single step with one brew update at the top.'
|
|
140
|
+
- 'Set HOMEBREW_NO_AUTO_UPDATE=1 as a job-level env var and call brew update explicitly only in the one step that needs it.'
|
|
141
|
+
- 'Pin to macOS-14 (macos-14-xlarge) or use macOS-26 (which has different Homebrew behavior) if the regression is blocking critical workflows while runner-images#13965 is open.'
|
|
142
|
+
- 'Check your CI logs for the "lockf: 200: already locked" error if macOS-15 Arm64 workflows started failing around late April 2026 — this regression is the likely cause.'
|
|
143
|
+
docs:
|
|
144
|
+
- url: 'https://github.com/actions/runner-images/issues/13965'
|
|
145
|
+
label: 'actions/runner-images#13965 — Running brew update twice in one workflow breaks (open, April 2026)'
|
|
146
|
+
- url: 'https://docs.brew.sh/Manpage#environment'
|
|
147
|
+
label: 'Homebrew docs — HOMEBREW_NO_AUTO_UPDATE environment variable'
|
|
148
|
+
- url: 'https://github.com/mullvad/mullvadvpn-app/actions/runs/24890005834'
|
|
149
|
+
label: 'mullvad/mullvadvpn-app — Example failing run (regression confirmed between image 20260415 and 20260422)'
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
id: triggers-071
|
|
2
|
+
title: 'on: branches: Filter with Only Negation Patterns Silently Never Triggers'
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- branches-filter
|
|
7
|
+
- push
|
|
8
|
+
- pull_request
|
|
9
|
+
- negation
|
|
10
|
+
- glob
|
|
11
|
+
- workflow-not-triggering
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'branches:\s*\n(\s+-\s+[''"]?!)'
|
|
14
|
+
flags: 'm'
|
|
15
|
+
- regex: 'branches:\s*\[\s*[''"]?!'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "# No error message — workflow simply never appears in the Actions run queue"
|
|
19
|
+
root_cause: |
|
|
20
|
+
GitHub Actions branch filters evaluate patterns sequentially against the ref name. The
|
|
21
|
+
documented rule is: "the workflow only runs if at least one pattern matches the ref name."
|
|
22
|
+
|
|
23
|
+
When a branches: (or branches-ignore's inverse: branches:) filter list contains ONLY
|
|
24
|
+
negation patterns (entries starting with !), no positive match is ever established.
|
|
25
|
+
Negation patterns can only EXCLUDE from an existing positive match set — they cannot
|
|
26
|
+
create a match on their own. The evaluation starts with zero matches, negations find
|
|
27
|
+
nothing to remove, and the result is always "no match" → workflow never fires.
|
|
28
|
+
|
|
29
|
+
This is a silent failure: no error is raised, no annotation appears, and the workflow
|
|
30
|
+
simply never shows up in the Actions tab when the target branch is pushed to. It is
|
|
31
|
+
especially confusing because the workflow file is syntactically valid and GitHub accepts it.
|
|
32
|
+
|
|
33
|
+
Common mistake patterns:
|
|
34
|
+
branches:
|
|
35
|
+
- '!main' # ← Only a negation — zero positive matches → never triggers
|
|
36
|
+
|
|
37
|
+
branches:
|
|
38
|
+
- '!master' # ← Same problem — always zero triggers
|
|
39
|
+
- '!release/**'
|
|
40
|
+
|
|
41
|
+
The branches-ignore filter does NOT have this problem because it operates on the
|
|
42
|
+
complement: it matches everything EXCEPT the listed patterns. Use branches-ignore when
|
|
43
|
+
you want to exclude specific branches.
|
|
44
|
+
|
|
45
|
+
Source: SO q/57699839 (144 votes) "GitHub Actions: how to target all branches EXCEPT
|
|
46
|
+
master?" — accepted answer (242 votes) documents the required positive+negative combo.
|
|
47
|
+
fix: |
|
|
48
|
+
To target all branches EXCEPT specific ones, choose one of two approaches:
|
|
49
|
+
|
|
50
|
+
Option 1 — Add wildcard positive patterns before the negation (order matters):
|
|
51
|
+
|
|
52
|
+
on:
|
|
53
|
+
push:
|
|
54
|
+
branches:
|
|
55
|
+
- '*' # matches every branch without a '/' (e.g. main, develop)
|
|
56
|
+
- '*/*' # matches single-slash branches (e.g. feature/x)
|
|
57
|
+
- '**' # matches all remaining branches
|
|
58
|
+
- '!main' # now excludes main from the positive matches above
|
|
59
|
+
|
|
60
|
+
Option 2 — Use branches-ignore instead (simpler and cleaner):
|
|
61
|
+
|
|
62
|
+
on:
|
|
63
|
+
push:
|
|
64
|
+
branches-ignore:
|
|
65
|
+
- main
|
|
66
|
+
- 'release/**'
|
|
67
|
+
|
|
68
|
+
Do NOT combine branches: and branches-ignore: on the same event — GitHub rejects that
|
|
69
|
+
combination with a YAML validation error.
|
|
70
|
+
fix_code:
|
|
71
|
+
- language: yaml
|
|
72
|
+
label: 'Broken — only negation in branches: filter, workflow never runs'
|
|
73
|
+
code: |
|
|
74
|
+
# This workflow NEVER triggers for any branch push — negation-only matches nothing:
|
|
75
|
+
on:
|
|
76
|
+
push:
|
|
77
|
+
branches:
|
|
78
|
+
- '!main' # WRONG: no positive pattern to negate from
|
|
79
|
+
|
|
80
|
+
- language: yaml
|
|
81
|
+
label: 'Fixed — positive wildcard patterns before negation'
|
|
82
|
+
code: |
|
|
83
|
+
# Correct approach: include positive patterns first, then exclude specific branches:
|
|
84
|
+
on:
|
|
85
|
+
push:
|
|
86
|
+
branches:
|
|
87
|
+
- '*' # matches branches without '/' in name
|
|
88
|
+
- '*/*' # matches single-level slash branches
|
|
89
|
+
- '**' # matches all remaining branches
|
|
90
|
+
- '!main' # now excludes main from the above positive matches
|
|
91
|
+
|
|
92
|
+
- language: yaml
|
|
93
|
+
label: 'Alternative fix — use branches-ignore (simplest for exclusion only)'
|
|
94
|
+
code: |
|
|
95
|
+
# Use branches-ignore when you want to exclude specific branches entirely:
|
|
96
|
+
on:
|
|
97
|
+
push:
|
|
98
|
+
branches-ignore:
|
|
99
|
+
- main
|
|
100
|
+
- 'release/**'
|
|
101
|
+
- 'hotfix/**'
|
|
102
|
+
pull_request:
|
|
103
|
+
branches-ignore:
|
|
104
|
+
- main
|
|
105
|
+
|
|
106
|
+
prevention:
|
|
107
|
+
- 'When you want to exclude branches, prefer branches-ignore: over branches: with negation — it is simpler and has no positive-pattern requirement.'
|
|
108
|
+
- 'If you must use branches: with negation, always include at least one positive glob (like ** or *) before the negation patterns.'
|
|
109
|
+
- 'Remember that pattern order matters: a positive match AFTER a negative pattern re-includes the ref; a negative match AFTER a positive match excludes it.'
|
|
110
|
+
- 'Use actionlint or act --list to verify your workflow would trigger before relying on pushes to test it.'
|
|
111
|
+
docs:
|
|
112
|
+
- url: 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onpushbranchestagsbranches-ignoretags-ignore'
|
|
113
|
+
label: 'GitHub Docs — on.push.branches filter syntax and negation patterns'
|
|
114
|
+
- url: 'https://stackoverflow.com/questions/57699839/github-actions-how-to-target-all-branches-except-master'
|
|
115
|
+
label: 'SO q/57699839 (144 votes) — GitHub Actions: how to target all branches EXCEPT master?'
|
|
116
|
+
- url: 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet'
|
|
117
|
+
label: 'GitHub Docs — Filter pattern cheat sheet (glob syntax reference)'
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
id: yaml-syntax-074
|
|
2
|
+
title: 'Workflow YAML File in Nested Subdirectory of .github/workflows/ Is Silently Ignored'
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow-placement
|
|
7
|
+
- subdirectory
|
|
8
|
+
- nested-folder
|
|
9
|
+
- file-location
|
|
10
|
+
- workflow-not-appearing
|
|
11
|
+
- silent-failure
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: '\.github/workflows/[^/\s]+/[^/\s]+\.ya?ml'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- "# No error — workflow YAML in .github/workflows/subdir/name.yml is silently ignored"
|
|
17
|
+
- "# Workflow never appears in Actions tab; no annotation is created"
|
|
18
|
+
root_cause: |
|
|
19
|
+
GitHub Actions only scans for workflow files in the DIRECT children of
|
|
20
|
+
`.github/workflows/` — it does NOT recurse into subdirectories. Files placed in
|
|
21
|
+
nested paths such as:
|
|
22
|
+
|
|
23
|
+
.github/workflows/ci/build.yml
|
|
24
|
+
.github/workflows/scripts/deploy.yaml
|
|
25
|
+
.github/workflows/reusable/my-caller.yml
|
|
26
|
+
|
|
27
|
+
are completely invisible to GitHub Actions. The runner does not parse them, does not
|
|
28
|
+
validate them, and emits no error or warning of any kind. The workflow simply never
|
|
29
|
+
appears in the repository's Actions tab, regardless of how correct the YAML content is.
|
|
30
|
+
|
|
31
|
+
This is a documented but easy-to-overlook constraint in the GitHub Actions architecture.
|
|
32
|
+
The scanner uses a shallow file glob equivalent to `.github/workflows/*.yml` and
|
|
33
|
+
`.github/workflows/*.yaml` — NOT `.github/workflows/**/*.yml`.
|
|
34
|
+
|
|
35
|
+
Common causes:
|
|
36
|
+
1. Developers organize workflows into subdirectories for clarity (e.g., `ci/`, `deploy/`),
|
|
37
|
+
not realizing GitHub won't pick them up.
|
|
38
|
+
2. Copy-pasting a workflow into a folder that already holds related shell scripts or
|
|
39
|
+
configuration files, creating an unintended nested path.
|
|
40
|
+
3. Renaming or restructuring the .github directory and accidentally moving workflow files
|
|
41
|
+
one level too deep.
|
|
42
|
+
4. Reusable workflow files placed in a `reusable/` or `shared/` subdirectory under
|
|
43
|
+
workflows/ — these also won't be discovered by GitHub as callable workflows.
|
|
44
|
+
|
|
45
|
+
Note: This affects ALL workflow types — regular workflows, reusable workflows
|
|
46
|
+
(workflow_call), scheduled workflows, and manually dispatched workflows alike.
|
|
47
|
+
|
|
48
|
+
Source: SO q/61989951 answer (score 9, from the 158-vote "GitHub Action workflow not
|
|
49
|
+
running" thread) and GitHub Actions documentation.
|
|
50
|
+
fix: |
|
|
51
|
+
Move all workflow YAML files directly into `.github/workflows/` (one level deep).
|
|
52
|
+
Do not create subdirectories inside `.github/workflows/` for workflow YAML files.
|
|
53
|
+
|
|
54
|
+
If you want to organize workflows logically, use naming prefixes instead of folders:
|
|
55
|
+
|
|
56
|
+
.github/workflows/ci-build.yml
|
|
57
|
+
.github/workflows/ci-test.yml
|
|
58
|
+
.github/workflows/deploy-staging.yml
|
|
59
|
+
.github/workflows/deploy-production.yml
|
|
60
|
+
|
|
61
|
+
For scripts, configs, and helper files that are referenced by workflows, place them
|
|
62
|
+
in a separate directory OUTSIDE `.github/workflows/`, for example:
|
|
63
|
+
.github/scripts/
|
|
64
|
+
.github/actions/my-local-action/
|
|
65
|
+
|
|
66
|
+
Note: Local composite actions (in `.github/actions/`) CAN be in subdirectories — the
|
|
67
|
+
subdirectory restriction only applies to workflow YAML files inside `.github/workflows/`.
|
|
68
|
+
fix_code:
|
|
69
|
+
- language: yaml
|
|
70
|
+
label: 'Broken — workflow file in nested subdirectory (silently ignored)'
|
|
71
|
+
code: |
|
|
72
|
+
# .github/workflows/ci/build.yml ← WRONG LOCATION, never discovered
|
|
73
|
+
name: Build CI
|
|
74
|
+
|
|
75
|
+
on:
|
|
76
|
+
push:
|
|
77
|
+
branches: ['**']
|
|
78
|
+
|
|
79
|
+
jobs:
|
|
80
|
+
build:
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
steps:
|
|
83
|
+
- uses: actions/checkout@v4
|
|
84
|
+
- run: npm ci && npm run build
|
|
85
|
+
|
|
86
|
+
- language: yaml
|
|
87
|
+
label: 'Fixed — workflow file at root of .github/workflows/'
|
|
88
|
+
code: |
|
|
89
|
+
# .github/workflows/ci-build.yml ← CORRECT LOCATION, discovered by GitHub
|
|
90
|
+
name: Build CI
|
|
91
|
+
|
|
92
|
+
on:
|
|
93
|
+
push:
|
|
94
|
+
branches: ['**']
|
|
95
|
+
|
|
96
|
+
jobs:
|
|
97
|
+
build:
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/checkout@v4
|
|
101
|
+
- run: npm ci && npm run build
|
|
102
|
+
|
|
103
|
+
- language: yaml
|
|
104
|
+
label: 'Organization pattern — use prefixes instead of subdirectories'
|
|
105
|
+
code: |
|
|
106
|
+
# Use name prefixes to group related workflows at the root level:
|
|
107
|
+
# .github/workflows/ci-build.yml
|
|
108
|
+
# .github/workflows/ci-lint.yml
|
|
109
|
+
# .github/workflows/ci-test.yml
|
|
110
|
+
# .github/workflows/deploy-staging.yml
|
|
111
|
+
# .github/workflows/deploy-production.yml
|
|
112
|
+
# .github/workflows/release-tag.yml
|
|
113
|
+
#
|
|
114
|
+
# Helper scripts/configs can be in subdirectories outside workflows/:
|
|
115
|
+
# .github/scripts/deploy.sh
|
|
116
|
+
# .github/actions/my-composite-action/action.yml ← composite actions CAN be nested
|
|
117
|
+
|
|
118
|
+
prevention:
|
|
119
|
+
- 'Keep all workflow YAML files (.yml / .yaml) directly in .github/workflows/ — never in subdirectories of that folder.'
|
|
120
|
+
- 'For workflow organization, use descriptive filename prefixes (ci-, deploy-, release-) instead of subdirectories.'
|
|
121
|
+
- 'Place helper shell scripts in .github/scripts/ and composite actions in .github/actions/<name>/ — subdirectories are fine there but not for workflow files.'
|
|
122
|
+
- 'After creating a new workflow file, immediately check that it appears in the repository Actions tab before relying on it for CI.'
|
|
123
|
+
- 'Use actionlint or the GitHub Actions VS Code extension to validate placement — both tools warn about unrecognized workflow file locations.'
|
|
124
|
+
docs:
|
|
125
|
+
- url: 'https://docs.github.com/en/actions/using-workflows/about-workflows#workflow-basics'
|
|
126
|
+
label: 'GitHub Docs — About workflows: file placement requirements'
|
|
127
|
+
- url: 'https://stackoverflow.com/questions/61989951/github-action-workflow-not-running'
|
|
128
|
+
label: 'SO q/61989951 (158 votes) — GitHub Action workflow not running (answer: nested subfolder not recognized)'
|
|
129
|
+
- url: 'https://docs.github.com/en/actions/creating-actions/creating-a-composite-action'
|
|
130
|
+
label: 'GitHub Docs — Composite actions (can be in .github/actions/ subdirectories)'
|
package/package.json
CHANGED