@htekdev/actions-debugger 1.0.124 → 1.0.125
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-073.yml +100 -0
- package/errors/caching-artifacts/caching-artifacts-074.yml +117 -0
- package/errors/known-unsolved/known-unsolved-071.yml +122 -0
- package/errors/known-unsolved/known-unsolved-072.yml +143 -0
- package/errors/permissions-auth/permissions-auth-071.yml +144 -0
- package/errors/permissions-auth/permissions-auth-072.yml +112 -0
- package/errors/permissions-auth/permissions-auth-073.yml +127 -0
- package/errors/permissions-auth/permissions-auth-074.yml +106 -0
- package/errors/permissions-auth/permissions-auth-075.yml +137 -0
- package/errors/runner-environment/runner-environment-227.yml +106 -0
- package/errors/runner-environment/runner-environment-228.yml +117 -0
- package/errors/runner-environment/runner-environment-229.yml +119 -0
- package/errors/runner-environment/runner-environment-230.yml +129 -0
- package/errors/runner-environment/runner-environment-231.yml +90 -0
- package/errors/runner-environment/runner-environment-232.yml +131 -0
- package/errors/runner-environment/runner-environment-233.yml +90 -0
- package/errors/runner-environment/runner-environment-234.yml +114 -0
- package/errors/runner-environment/runner-environment-235.yml +151 -0
- package/errors/silent-failures/silent-failures-112.yml +97 -0
- package/errors/silent-failures/silent-failures-113.yml +110 -0
- package/errors/silent-failures/silent-failures-114.yml +116 -0
- package/errors/silent-failures/silent-failures-115.yml +130 -0
- package/errors/silent-failures/silent-failures-116.yml +117 -0
- package/errors/silent-failures/silent-failures-117.yml +137 -0
- package/errors/silent-failures/silent-failures-118.yml +156 -0
- package/errors/yaml-syntax/yaml-syntax-075.yml +128 -0
- package/errors/yaml-syntax/yaml-syntax-076.yml +107 -0
- package/package.json +1 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
id: caching-artifacts-073
|
|
2
|
+
title: 'actions/upload-artifact and runner blob uploads stall or fail with Bad Request through HTTPS_PROXY — BlobClient missing proxy transport'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- upload-artifact
|
|
7
|
+
- proxy
|
|
8
|
+
- https-proxy
|
|
9
|
+
- azure-blob
|
|
10
|
+
- self-hosted
|
|
11
|
+
- blob-client
|
|
12
|
+
- bad-request
|
|
13
|
+
- no-proxy
|
|
14
|
+
patterns:
|
|
15
|
+
- regex: 'Beginning upload of artifact content to blob storage'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: '^Error: Bad Request$'
|
|
18
|
+
flags: 'im'
|
|
19
|
+
- regex: 'CONNECT.*blob\.core\.windows\.net.*200.*ALLOWED'
|
|
20
|
+
flags: 'i'
|
|
21
|
+
- regex: 'latency=\d+\.\d+s.*stall|stall.*blob.*proxy'
|
|
22
|
+
flags: 'i'
|
|
23
|
+
error_messages:
|
|
24
|
+
- 'Beginning upload of artifact content to blob storage'
|
|
25
|
+
- 'Error: Bad Request'
|
|
26
|
+
- 'CONNECT productionresultssa*.blob.core.windows.net:443 → status=200 (ALLOWED)'
|
|
27
|
+
root_cause: |
|
|
28
|
+
When a self-hosted runner is behind an HTTPS forward proxy (HTTPS_PROXY / https_proxy env var),
|
|
29
|
+
artifact uploads (actions/upload-artifact) and runner-internal uploads (step summaries, job logs,
|
|
30
|
+
diagnostics) stall or fail with "Error: Bad Request".
|
|
31
|
+
|
|
32
|
+
Root cause — two layers, same problem:
|
|
33
|
+
|
|
34
|
+
1. @actions/artifact (TypeScript, used by upload-artifact@v4-v7) creates a BlobClient from
|
|
35
|
+
@azure/storage-blob with only the authenticated URL:
|
|
36
|
+
new BlobClient(authenticatedUploadURL)
|
|
37
|
+
No StoragePipelineOptions with proxy configuration are passed. The Azure SDK builds its own
|
|
38
|
+
HTTP pipeline without proxy transport, so even when HTTPS_PROXY is set in the environment, the
|
|
39
|
+
SDK does not correctly route the CONNECT tunnel.
|
|
40
|
+
|
|
41
|
+
2. The runner's .NET ResultsHttpClient (used for step summaries, logs, diagnostics) also creates a
|
|
42
|
+
BlobClient without proxy transport options (runner#4351).
|
|
43
|
+
|
|
44
|
+
Proxy logs show:
|
|
45
|
+
- CONNECT tunnel to *.blob.core.windows.net:443 succeeds (HTTP 200 ALLOWED).
|
|
46
|
+
- Only ~17 KB of the payload is transmitted.
|
|
47
|
+
- The connection stalls for ~75 seconds and returns a "Bad Request" response.
|
|
48
|
+
- curl / Python / .NET HttpClient all upload successfully to the same endpoint in <1 second.
|
|
49
|
+
|
|
50
|
+
The upload step logs show the artifact upload starting but no "Artifact successfully finalized" line,
|
|
51
|
+
followed immediately by "Error: Bad Request". The step fails with no further detail.
|
|
52
|
+
fix: |
|
|
53
|
+
Add the Azure Blob Storage hostname to NO_PROXY to bypass the HTTPS proxy for all blob traffic.
|
|
54
|
+
The upload URL already contains a time-limited SAS token for authentication, so bypassing the
|
|
55
|
+
proxy for this destination does not weaken security in most configurations.
|
|
56
|
+
|
|
57
|
+
Set NO_PROXY (and no_proxy for case-insensitive tools) to include .blob.core.windows.net.
|
|
58
|
+
|
|
59
|
+
This can be set:
|
|
60
|
+
- In the runner's .env file (persists across all jobs on the runner)
|
|
61
|
+
- As job-level env: in the workflow (overrides only for that job)
|
|
62
|
+
fix_code:
|
|
63
|
+
- language: bash
|
|
64
|
+
label: 'Persist NO_PROXY in the runner service .env file'
|
|
65
|
+
code: |
|
|
66
|
+
# Append to the runner .env file (path varies by install location)
|
|
67
|
+
echo 'NO_PROXY=.blob.core.windows.net' >> /home/runner/actions-runner/.env
|
|
68
|
+
echo 'no_proxy=.blob.core.windows.net' >> /home/runner/actions-runner/.env
|
|
69
|
+
# Restart the runner service to pick up the change
|
|
70
|
+
sudo systemctl restart actions.runner.*.service
|
|
71
|
+
|
|
72
|
+
- language: yaml
|
|
73
|
+
label: 'Set NO_PROXY per workflow job to bypass proxy for artifact uploads'
|
|
74
|
+
code: |
|
|
75
|
+
jobs:
|
|
76
|
+
build:
|
|
77
|
+
runs-on: [self-hosted]
|
|
78
|
+
env:
|
|
79
|
+
# Bypass HTTPS proxy for Azure Blob Storage — prevents BlobClient stall
|
|
80
|
+
NO_PROXY: '.blob.core.windows.net'
|
|
81
|
+
no_proxy: '.blob.core.windows.net'
|
|
82
|
+
steps:
|
|
83
|
+
- uses: actions/checkout@v4
|
|
84
|
+
- run: ./build.sh
|
|
85
|
+
- uses: actions/upload-artifact@v6
|
|
86
|
+
with:
|
|
87
|
+
name: build-output
|
|
88
|
+
path: ./dist/
|
|
89
|
+
prevention:
|
|
90
|
+
- 'Always test artifact uploads when deploying self-hosted runners behind an HTTPS forward proxy before putting runners into production.'
|
|
91
|
+
- 'Set NO_PROXY=.blob.core.windows.net in the runner environment before rolling out proxy-configured runners.'
|
|
92
|
+
- 'Watch proxy logs for 75-second CONNECT stalls to *.blob.core.windows.net as the signature for this issue.'
|
|
93
|
+
- 'Runner-internal uploads (step summaries, job logs) are also affected — verify both artifact and job-summary visibility when testing.'
|
|
94
|
+
docs:
|
|
95
|
+
- url: 'https://github.com/actions/toolkit/issues/2377'
|
|
96
|
+
label: 'actions/toolkit#2377 — @actions/artifact BlobClient missing proxy transport (open)'
|
|
97
|
+
- url: 'https://github.com/actions/runner/issues/4351'
|
|
98
|
+
label: 'actions/runner#4351 — runner Azure Blob uploads stall through HTTPS proxy (open)'
|
|
99
|
+
- url: 'https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#communication-between-self-hosted-runners-and-github'
|
|
100
|
+
label: 'GitHub Docs — Self-hosted runner network communication requirements'
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
id: caching-artifacts-074
|
|
2
|
+
title: 'actions/cache restore silently treats 429 rate limit as cache miss — no retry, full rebuild forced'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- rate-limit
|
|
8
|
+
- 429
|
|
9
|
+
- restore
|
|
10
|
+
- cache-miss
|
|
11
|
+
- rebuild
|
|
12
|
+
- no-retry
|
|
13
|
+
- matrix
|
|
14
|
+
patterns:
|
|
15
|
+
- regex: "You've hit a rate limit"
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'Failed to restore.*Rate limited.*429'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
- regex: 'Failed to GetCacheEntryDownloadURL.*rate limit exceeded'
|
|
20
|
+
flags: 'i'
|
|
21
|
+
- regex: 'Too Many Requests.*rate limit exceeded'
|
|
22
|
+
flags: 'i'
|
|
23
|
+
error_messages:
|
|
24
|
+
- "Warning: You've hit a rate limit, your rate limit will reset in 18 seconds"
|
|
25
|
+
- "Warning: Failed to restore: Failed to GetCacheEntryDownloadURL: Rate limited: Failed request: (429) Too Many Requests: rate limit exceeded"
|
|
26
|
+
- "Cache not found for input keys: ..."
|
|
27
|
+
root_cause: |
|
|
28
|
+
When the GitHub Actions Cache Service rate-limits a restore request with HTTP 429, the cache
|
|
29
|
+
action (v4/v5) emits a warning and immediately falls back to "Cache not found" — treating the
|
|
30
|
+
rate limit as a permanent cache miss rather than a transient error worth retrying.
|
|
31
|
+
|
|
32
|
+
The cache action does not:
|
|
33
|
+
- Retry the restore after the rate-limit reset period (reported in the warning, typically 10-60s).
|
|
34
|
+
- Fail the step with a hard error so the developer is alerted to an infrastructure issue.
|
|
35
|
+
- Implement any exponential backoff on the restore path.
|
|
36
|
+
|
|
37
|
+
Downstream steps see only "Cache not found for input keys: ..." and proceed to rebuild
|
|
38
|
+
dependencies from scratch, as if the cache had never been saved. The real cause — a transient
|
|
39
|
+
429 from the cache service — is easily missed because it appears only as a "Warning:" line
|
|
40
|
+
mid-step, several lines before the final "Cache not found" output.
|
|
41
|
+
|
|
42
|
+
This is most disruptive in large parallel matrix workflows where many concurrent jobs all hit
|
|
43
|
+
the cache service simultaneously. The cache service rate-limits the burst, every affected job
|
|
44
|
+
sees a cache miss, and the entire matrix rebuilds from scratch. CI time can multiply by 5-10x
|
|
45
|
+
with no clear indication in the job summary that the rebuilds were avoidable.
|
|
46
|
+
fix: |
|
|
47
|
+
While the cache action does not yet implement automatic retry on 429, you can reduce the impact:
|
|
48
|
+
|
|
49
|
+
1. Limit max-parallel on matrix strategies to reduce simultaneous cache restore bursts.
|
|
50
|
+
|
|
51
|
+
2. Use restore-keys as a fallback: even if the exact-key restore is rate-limited, a prefix
|
|
52
|
+
match restore-keys request may succeed (different cache entry, different cache service shard).
|
|
53
|
+
|
|
54
|
+
3. Stagger cache-heavy workflows using concurrency groups or needs: dependencies so they don't
|
|
55
|
+
all restore caches at the same second.
|
|
56
|
+
|
|
57
|
+
4. Upgrade to the latest cache action — retry logic for 429 is a tracked improvement in
|
|
58
|
+
actions/cache#1758.
|
|
59
|
+
fix_code:
|
|
60
|
+
- language: yaml
|
|
61
|
+
label: 'Use restore-keys as a fallback to reduce full rebuilds on 429 rate limit'
|
|
62
|
+
code: |
|
|
63
|
+
- uses: actions/cache@v4
|
|
64
|
+
id: cache
|
|
65
|
+
with:
|
|
66
|
+
path: ~/.npm
|
|
67
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
68
|
+
# Fallback: match any npm cache for this OS — may succeed even when exact key is rate-limited
|
|
69
|
+
restore-keys: |
|
|
70
|
+
${{ runner.os }}-npm-
|
|
71
|
+
|
|
72
|
+
- language: yaml
|
|
73
|
+
label: 'Limit matrix parallelism to reduce simultaneous cache restore bursts'
|
|
74
|
+
code: |
|
|
75
|
+
jobs:
|
|
76
|
+
build:
|
|
77
|
+
strategy:
|
|
78
|
+
matrix:
|
|
79
|
+
target: [linux-x64, linux-arm64, windows-x64, macos-x64, macos-arm64]
|
|
80
|
+
# Limit concurrent cache restores — burst of 2 is much less likely to
|
|
81
|
+
# trigger 429 than a burst of 5 hitting the cache service simultaneously.
|
|
82
|
+
max-parallel: 2
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/cache@v4
|
|
86
|
+
with:
|
|
87
|
+
path: ~/.cache
|
|
88
|
+
key: ${{ matrix.target }}-deps-${{ hashFiles('**/Cargo.lock') }}
|
|
89
|
+
restore-keys: |
|
|
90
|
+
${{ matrix.target }}-deps-
|
|
91
|
+
|
|
92
|
+
- language: yaml
|
|
93
|
+
label: 'Stagger cache-restore-heavy jobs using concurrency groups'
|
|
94
|
+
code: |
|
|
95
|
+
jobs:
|
|
96
|
+
restore-cache:
|
|
97
|
+
concurrency:
|
|
98
|
+
group: cache-restore-${{ github.ref }}
|
|
99
|
+
cancel-in-progress: false # queue, not cancel
|
|
100
|
+
runs-on: ubuntu-latest
|
|
101
|
+
steps:
|
|
102
|
+
- uses: actions/cache@v4
|
|
103
|
+
with:
|
|
104
|
+
path: ~/.gradle/caches
|
|
105
|
+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
|
|
106
|
+
prevention:
|
|
107
|
+
- 'Never run more than ~8 simultaneous cache restore operations in the same repository — the cache service rate limit is per repository.'
|
|
108
|
+
- 'Always include restore-keys as a fallback so partial cache hits reduce rebuild cost when the exact key is rate-limited.'
|
|
109
|
+
- 'Watch for the "Warning: You''ve hit a rate limit" log line when investigating unexpectedly slow CI builds.'
|
|
110
|
+
- 'Treat "Cache not found" as potentially a transient 429, not necessarily a first-run or key-miss, especially in high-parallelism workflows.'
|
|
111
|
+
docs:
|
|
112
|
+
- url: 'https://github.com/actions/cache/issues/1758'
|
|
113
|
+
label: 'actions/cache#1758 — Handle rate limit with retry instead of silent cache miss (open)'
|
|
114
|
+
- url: 'https://github.com/actions/cache#inputs'
|
|
115
|
+
label: 'actions/cache — restore-keys documentation'
|
|
116
|
+
- url: 'https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows'
|
|
117
|
+
label: 'GitHub Docs — Caching dependencies to speed up workflows'
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
id: known-unsolved-071
|
|
2
|
+
title: 'Actions cache is repository-scoped — cannot be shared across repositories in the same organization'
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- cross-repo
|
|
8
|
+
- organization
|
|
9
|
+
- scope
|
|
10
|
+
- monorepo
|
|
11
|
+
- limitation
|
|
12
|
+
- cache-isolation
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'Cache not found for input keys.*(?:cross-repo|other.repo|shared.cache)'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'No cache found.*(?:cross-repo|other.repo|shared)'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
error_messages:
|
|
19
|
+
- 'Cache not found for input keys: ...'
|
|
20
|
+
- 'No cache found'
|
|
21
|
+
root_cause: |
|
|
22
|
+
The GitHub Actions cache service scopes all cache entries to the repository where they
|
|
23
|
+
were created. There is no mechanism to share a cache entry between two different
|
|
24
|
+
repositories, even within the same organization.
|
|
25
|
+
|
|
26
|
+
Cache access rules per the GitHub documentation:
|
|
27
|
+
- A workflow can restore caches created in the current branch, the default branch (main),
|
|
28
|
+
or (for pull requests) the base branch including base branches of forks.
|
|
29
|
+
- "Cross-branch" access is supported within the same repository.
|
|
30
|
+
- There is NO "cross-repository" access — a cache created in `org/repo-a` is
|
|
31
|
+
completely invisible to workflows running in `org/repo-b`.
|
|
32
|
+
|
|
33
|
+
This affects teams who:
|
|
34
|
+
- Manage related repositories that share build toolchains (e.g., a shared Go module cache
|
|
35
|
+
across a dozen microservices).
|
|
36
|
+
- Have split monorepos where a common library is built separately and cached.
|
|
37
|
+
- Want to cache a slow Docker layer in one repo and reuse it in a deployment repo.
|
|
38
|
+
|
|
39
|
+
The underlying reason is cache isolation as a security boundary: allowing cross-repo
|
|
40
|
+
cache access could leak build artifacts or credentials stored in the cache between
|
|
41
|
+
unrelated repositories.
|
|
42
|
+
|
|
43
|
+
There is no current GitHub Actions native solution for cross-repo cache sharing. The
|
|
44
|
+
GitHub roadmap has not publicly committed to this feature as of 2026.
|
|
45
|
+
fix: |
|
|
46
|
+
There is no built-in fix. Workarounds depend on your use case:
|
|
47
|
+
|
|
48
|
+
1. Publish shared artifacts to a package registry (GitHub Packages, npm, PyPI, Docker Hub).
|
|
49
|
+
Instead of caching, version-tag the shared artifact and consume it as a dependency.
|
|
50
|
+
This is the recommended approach for shared libraries and Docker base images.
|
|
51
|
+
|
|
52
|
+
2. Use a self-hosted runner with a shared filesystem. The runner's local disk or a
|
|
53
|
+
network share can act as a cross-repo cache. Use the `path:` input of actions/cache
|
|
54
|
+
pointing to a shared mount. Cache hits and misses are managed manually via key files.
|
|
55
|
+
|
|
56
|
+
3. Use a third-party caching backend (S3, GCS, Azure Blob, Artifactory) for build
|
|
57
|
+
artifacts that must be shared. Upload/download via CLI in workflow steps.
|
|
58
|
+
|
|
59
|
+
4. Consolidate the related repositories into a single repository (monorepo).
|
|
60
|
+
All workflows within the same repo can share cache entries.
|
|
61
|
+
fix_code:
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: 'Publish shared Docker base image to GHCR instead of caching across repos'
|
|
64
|
+
code: |
|
|
65
|
+
# repo-a: builds and publishes the shared base image
|
|
66
|
+
jobs:
|
|
67
|
+
publish-base:
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
permissions:
|
|
70
|
+
packages: write
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@v4
|
|
73
|
+
- uses: docker/login-action@v3
|
|
74
|
+
with:
|
|
75
|
+
registry: ghcr.io
|
|
76
|
+
username: ${{ github.actor }}
|
|
77
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
78
|
+
- uses: docker/build-push-action@v6
|
|
79
|
+
with:
|
|
80
|
+
context: ./base-image
|
|
81
|
+
push: true
|
|
82
|
+
tags: ghcr.io/${{ github.repository_owner }}/shared-base:latest
|
|
83
|
+
|
|
84
|
+
# repo-b: pulls the published image instead of relying on cache
|
|
85
|
+
jobs:
|
|
86
|
+
build:
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
container:
|
|
89
|
+
image: ghcr.io/myorg/shared-base:latest
|
|
90
|
+
credentials:
|
|
91
|
+
username: ${{ github.actor }}
|
|
92
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@v4
|
|
95
|
+
- run: ./build.sh
|
|
96
|
+
|
|
97
|
+
- language: yaml
|
|
98
|
+
label: 'Self-hosted runner shared-path cache as a cross-repo workaround'
|
|
99
|
+
code: |
|
|
100
|
+
# Both repo-a and repo-b workflows — same self-hosted runner, shared disk at /opt/shared-cache
|
|
101
|
+
jobs:
|
|
102
|
+
build:
|
|
103
|
+
runs-on: [self-hosted, linux, shared-cache]
|
|
104
|
+
steps:
|
|
105
|
+
- uses: actions/checkout@v4
|
|
106
|
+
- uses: actions/cache@v4
|
|
107
|
+
with:
|
|
108
|
+
path: /opt/shared-cache/gradle
|
|
109
|
+
# Key is independent of the repo — deliberately shared
|
|
110
|
+
key: gradle-${{ hashFiles('**/*.gradle*') }}
|
|
111
|
+
# NOTE: This bypasses GitHub's repo-scope isolation.
|
|
112
|
+
# Ensure the self-hosted runner pool is trusted and isolated.
|
|
113
|
+
- run: ./gradlew build
|
|
114
|
+
prevention:
|
|
115
|
+
- 'Design shared build artifacts as versioned dependencies (packages) from the start — avoids cross-repo cache needs entirely.'
|
|
116
|
+
- 'For Docker base images shared across repos, publish to a registry and reference by digest, not by mutable tags.'
|
|
117
|
+
- 'When adopting self-hosted runners for cross-repo cache sharing, audit what secrets and artifacts are accessible to all jobs that share the runner to avoid cross-contamination.'
|
|
118
|
+
docs:
|
|
119
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache'
|
|
120
|
+
label: 'GitHub Docs — Cache access restrictions and scoping'
|
|
121
|
+
- url: 'https://github.com/actions/cache/blob/main/tips-and-workarounds.md'
|
|
122
|
+
label: 'actions/cache — Tips and workarounds (cross-branch, cross-OS, but not cross-repo)'
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
id: known-unsolved-072
|
|
2
|
+
title: 'No parallel steps within a single job — all steps execute sequentially'
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- parallel-steps
|
|
7
|
+
- sequential
|
|
8
|
+
- performance
|
|
9
|
+
- job-structure
|
|
10
|
+
- limitation
|
|
11
|
+
- roadmap
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'parallel.*steps.*not.*support|steps.*run.*sequentially'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
error_messages:
|
|
16
|
+
- 'steps run sequentially — no native parallel step execution within a single job'
|
|
17
|
+
root_cause: |
|
|
18
|
+
In GitHub Actions, all steps within a single job execute sequentially in the order they
|
|
19
|
+
are defined. There is no native syntax to declare that two or more steps within the same
|
|
20
|
+
job should run concurrently.
|
|
21
|
+
|
|
22
|
+
This means:
|
|
23
|
+
- A job that runs `npm install`, `eslint`, and `jest` must run them one after another,
|
|
24
|
+
even if `eslint` and `jest` are completely independent and could run simultaneously.
|
|
25
|
+
- A job that runs two independent API calls, file downloads, or build targets must wait
|
|
26
|
+
for each to complete before starting the next.
|
|
27
|
+
- The only way to achieve true parallelism in GitHub Actions is to split work across
|
|
28
|
+
multiple jobs with `needs:` dependencies — but this requires each job to set up its
|
|
29
|
+
own runner, check out the repository, restore caches, and install dependencies,
|
|
30
|
+
adding significant overhead for short tasks.
|
|
31
|
+
|
|
32
|
+
This is a long-standing community request. GitHub added it to the public roadmap as
|
|
33
|
+
"Parallel Steps in GitHub Actions" (github/roadmap#1191, GA milestone, 2025).
|
|
34
|
+
|
|
35
|
+
Common workarounds add latency (multiple jobs) or complexity (background processes).
|
|
36
|
+
fix: |
|
|
37
|
+
There is no built-in fix. Workarounds:
|
|
38
|
+
|
|
39
|
+
1. Split parallel work into separate jobs using `needs:` and a matrix strategy.
|
|
40
|
+
Each job adds runner setup overhead (~15-30s), so this is most effective for
|
|
41
|
+
tasks that take minutes, not seconds.
|
|
42
|
+
|
|
43
|
+
2. Run steps as background shell processes and wait for them with `wait` (bash only).
|
|
44
|
+
This works for independent shell commands that don't need to write to GITHUB_OUTPUT,
|
|
45
|
+
GITHUB_ENV, or produce step outputs — those mechanisms are not safe for concurrent use.
|
|
46
|
+
|
|
47
|
+
3. Use `make -j N` or `./gradlew --parallel` or similar build-tool parallelism within
|
|
48
|
+
a single shell step. This parallelizes work inside one run: step without needing
|
|
49
|
+
multiple GitHub Actions steps.
|
|
50
|
+
|
|
51
|
+
4. Run a Docker Compose or docker run --detach to start background services, then
|
|
52
|
+
use a final step to check results.
|
|
53
|
+
fix_code:
|
|
54
|
+
- language: yaml
|
|
55
|
+
label: 'Run independent checks in parallel via separate jobs (preferred for long tasks)'
|
|
56
|
+
code: |
|
|
57
|
+
jobs:
|
|
58
|
+
lint:
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/checkout@v4
|
|
62
|
+
- uses: actions/setup-node@v4
|
|
63
|
+
with:
|
|
64
|
+
node-version: 22
|
|
65
|
+
cache: npm
|
|
66
|
+
- run: npm ci
|
|
67
|
+
- run: npm run lint
|
|
68
|
+
|
|
69
|
+
test:
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@v4
|
|
73
|
+
- uses: actions/setup-node@v4
|
|
74
|
+
with:
|
|
75
|
+
node-version: 22
|
|
76
|
+
cache: npm
|
|
77
|
+
- run: npm ci
|
|
78
|
+
- run: npm test
|
|
79
|
+
|
|
80
|
+
type-check:
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
steps:
|
|
83
|
+
- uses: actions/checkout@v4
|
|
84
|
+
- uses: actions/setup-node@v4
|
|
85
|
+
with:
|
|
86
|
+
node-version: 22
|
|
87
|
+
cache: npm
|
|
88
|
+
- run: npm ci
|
|
89
|
+
- run: npm run typecheck
|
|
90
|
+
|
|
91
|
+
# Final gate job waits for all parallel checks
|
|
92
|
+
ci-complete:
|
|
93
|
+
needs: [lint, test, type-check]
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
steps:
|
|
96
|
+
- run: echo "All checks passed"
|
|
97
|
+
|
|
98
|
+
- language: bash
|
|
99
|
+
label: 'Background shell processes for independent shell commands (same job)'
|
|
100
|
+
code: |
|
|
101
|
+
# In a single run: step, launch parallel shell processes and wait for all
|
|
102
|
+
# WARNING: This pattern does not work with GITHUB_OUTPUT/GITHUB_ENV/GITHUB_STEP_SUMMARY
|
|
103
|
+
# from the background processes — race conditions corrupt the append-mode files.
|
|
104
|
+
# Use only for commands that write to their own output files.
|
|
105
|
+
|
|
106
|
+
./fetch-data-source-1.sh > /tmp/source1.json &
|
|
107
|
+
PID1=$!
|
|
108
|
+
|
|
109
|
+
./fetch-data-source-2.sh > /tmp/source2.json &
|
|
110
|
+
PID2=$!
|
|
111
|
+
|
|
112
|
+
wait $PID1 || { echo "Source 1 fetch failed"; exit 1; }
|
|
113
|
+
wait $PID2 || { echo "Source 2 fetch failed"; exit 1; }
|
|
114
|
+
|
|
115
|
+
echo "Both sources fetched in parallel"
|
|
116
|
+
./merge-sources.py /tmp/source1.json /tmp/source2.json
|
|
117
|
+
|
|
118
|
+
- language: yaml
|
|
119
|
+
label: 'Use build tool parallelism inside a single step'
|
|
120
|
+
code: |
|
|
121
|
+
jobs:
|
|
122
|
+
build:
|
|
123
|
+
runs-on: ubuntu-latest
|
|
124
|
+
steps:
|
|
125
|
+
- uses: actions/checkout@v4
|
|
126
|
+
- uses: actions/setup-java@v4
|
|
127
|
+
with:
|
|
128
|
+
java-version: 21
|
|
129
|
+
distribution: temurin
|
|
130
|
+
# Gradle parallel project execution — all subprojects build concurrently
|
|
131
|
+
- run: ./gradlew build --parallel --max-workers 4
|
|
132
|
+
prevention:
|
|
133
|
+
- 'Design CI pipelines with parallel jobs from the start — split lint, test, and build into independent jobs to maximize parallelism today.'
|
|
134
|
+
- 'Use a shared cache with `actions/cache` to minimize the overhead of repeated `npm ci` / `pip install` across parallel jobs.'
|
|
135
|
+
- 'Track github/roadmap#1191 for the native parallel steps feature — once released, sequential-step bottlenecks can be eliminated without the multi-job overhead.'
|
|
136
|
+
- 'For build-tool tasks, always prefer build-native parallelism (`make -j`, `--parallel`, `cargo build --jobs`) over workflow-level workarounds.'
|
|
137
|
+
docs:
|
|
138
|
+
- url: 'https://github.com/github/roadmap/issues/1191'
|
|
139
|
+
label: 'github/roadmap#1191 — Parallel Steps in GitHub Actions (GA milestone, open 2025)'
|
|
140
|
+
- url: 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idsteps'
|
|
141
|
+
label: 'GitHub Docs — jobs.<job_id>.steps (sequential execution model)'
|
|
142
|
+
- url: 'https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs'
|
|
143
|
+
label: 'GitHub Docs — Using a matrix for parallel jobs as a workaround'
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
id: permissions-auth-071
|
|
2
|
+
title: 'OIDC token not available in pull_request events from forks — id-token: write cannot be granted'
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- oidc
|
|
7
|
+
- fork
|
|
8
|
+
- pull-request
|
|
9
|
+
- id-token
|
|
10
|
+
- attestation
|
|
11
|
+
- aws
|
|
12
|
+
- azure
|
|
13
|
+
- workload-identity
|
|
14
|
+
patterns:
|
|
15
|
+
- regex: 'Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
- regex: 'Could not fetch an OIDC token.*id-token.*write'
|
|
18
|
+
flags: 'i'
|
|
19
|
+
- regex: 'Error: Action failed with error: Error: Error message: Unable to get ACTIONS_ID_TOKEN_REQUEST_URL'
|
|
20
|
+
flags: 'i'
|
|
21
|
+
error_messages:
|
|
22
|
+
- 'Error: Error message: Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable'
|
|
23
|
+
- 'Could not fetch an OIDC token. Did you remember to add `id-token: write` to your workflow permissions?'
|
|
24
|
+
- 'Error: Action failed with error: Error: Error message: Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable'
|
|
25
|
+
root_cause: |
|
|
26
|
+
GitHub Actions workflows triggered by `pull_request` events from forked repositories cannot
|
|
27
|
+
mint OIDC tokens, even when `id-token: write` is explicitly set in the workflow `permissions:` block.
|
|
28
|
+
|
|
29
|
+
The restriction is enforced by GitHub's security model for fork pull requests:
|
|
30
|
+
- Fork PRs run with the permissions of the fork, not the base repository.
|
|
31
|
+
- GitHub documentation states: "You can use the permissions key to add and remove read
|
|
32
|
+
permissions for forked repositories, but typically you can't grant write access."
|
|
33
|
+
- The `id-token: write` scope is a WRITE permission. The ACTIONS_ID_TOKEN_REQUEST_URL
|
|
34
|
+
environment variable is only set when the runtime has write-level token access.
|
|
35
|
+
- When the variable is absent, any action or toolkit call to `core.getIDToken()` fails with
|
|
36
|
+
"Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable".
|
|
37
|
+
|
|
38
|
+
This commonly impacts:
|
|
39
|
+
- Cloud provider OIDC authentication (AWS configure-aws-credentials, Azure login, GCP auth)
|
|
40
|
+
- actions/attest-build-provenance and actions/attest for build attestation on PRs
|
|
41
|
+
- Any action that relies on `@actions/core` `getIDToken()` for OIDC federation
|
|
42
|
+
|
|
43
|
+
The restriction exists to prevent malicious fork contributors from using OIDC tokens to
|
|
44
|
+
authenticate against production cloud accounts or push malicious artifacts.
|
|
45
|
+
fix: |
|
|
46
|
+
Option 1 — Use `pull_request_target` instead of `pull_request` (CAUTION required).
|
|
47
|
+
`pull_request_target` runs in the context of the base repository and CAN mint OIDC tokens.
|
|
48
|
+
However, it executes in the base repo's context with full access to base repo secrets —
|
|
49
|
+
this is a significant security risk if the workflow checks out or executes fork code without
|
|
50
|
+
proper isolation. Read all security guides before using this trigger.
|
|
51
|
+
|
|
52
|
+
Option 2 — Split the workflow into two parts.
|
|
53
|
+
Use `pull_request` for the build/test phase (no OIDC). Use a separate workflow triggered
|
|
54
|
+
by `workflow_run` or manual approval to perform OIDC-protected operations (attestation, deploy)
|
|
55
|
+
after verifying the PR code is safe.
|
|
56
|
+
|
|
57
|
+
Option 3 — Use a GitHub App token with explicit repository permissions for operations
|
|
58
|
+
that do not strictly require OIDC (e.g., package publishing). This avoids the OIDC
|
|
59
|
+
restriction entirely.
|
|
60
|
+
|
|
61
|
+
Option 4 — Only run OIDC-dependent steps on `push` events to protected branches (post-merge),
|
|
62
|
+
not on `pull_request` events from forks. Guard OIDC steps with:
|
|
63
|
+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
|
|
64
|
+
fix_code:
|
|
65
|
+
- language: yaml
|
|
66
|
+
label: 'Guard OIDC steps — skip for fork PRs, run only for same-repo PRs or push'
|
|
67
|
+
code: |
|
|
68
|
+
jobs:
|
|
69
|
+
build-and-attest:
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
permissions:
|
|
72
|
+
id-token: write
|
|
73
|
+
contents: read
|
|
74
|
+
attestations: write
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@v4
|
|
77
|
+
|
|
78
|
+
- name: Build artifact
|
|
79
|
+
run: ./build.sh
|
|
80
|
+
|
|
81
|
+
# OIDC-dependent steps: skip if fork PR (id-token write not available)
|
|
82
|
+
- name: Attest build provenance
|
|
83
|
+
if: >
|
|
84
|
+
github.event_name != 'pull_request' ||
|
|
85
|
+
github.event.pull_request.head.repo.full_name == github.repository
|
|
86
|
+
uses: actions/attest-build-provenance@v2
|
|
87
|
+
with:
|
|
88
|
+
subject-path: './dist/app'
|
|
89
|
+
|
|
90
|
+
- language: yaml
|
|
91
|
+
label: 'Split workflow: use workflow_run to run OIDC steps after fork PR merges'
|
|
92
|
+
code: |
|
|
93
|
+
# Workflow 1: runs on pull_request — builds and uploads artifact (no OIDC)
|
|
94
|
+
on:
|
|
95
|
+
pull_request:
|
|
96
|
+
jobs:
|
|
97
|
+
build:
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/checkout@v4
|
|
101
|
+
- run: ./build.sh
|
|
102
|
+
- uses: actions/upload-artifact@v4
|
|
103
|
+
with:
|
|
104
|
+
name: build-output
|
|
105
|
+
path: ./dist/
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
# Workflow 2: runs after workflow 1 completes — performs OIDC operations
|
|
110
|
+
on:
|
|
111
|
+
workflow_run:
|
|
112
|
+
workflows: ['Build']
|
|
113
|
+
types: [completed]
|
|
114
|
+
jobs:
|
|
115
|
+
attest:
|
|
116
|
+
# Only runs on base-repo push events completing after a merge
|
|
117
|
+
if: github.event.workflow_run.conclusion == 'success'
|
|
118
|
+
runs-on: ubuntu-latest
|
|
119
|
+
permissions:
|
|
120
|
+
id-token: write
|
|
121
|
+
attestations: write
|
|
122
|
+
steps:
|
|
123
|
+
- uses: actions/download-artifact@v4
|
|
124
|
+
with:
|
|
125
|
+
name: build-output
|
|
126
|
+
run-id: ${{ github.event.workflow_run.id }}
|
|
127
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
128
|
+
- uses: actions/attest-build-provenance@v2
|
|
129
|
+
with:
|
|
130
|
+
subject-path: './dist/app'
|
|
131
|
+
prevention:
|
|
132
|
+
- 'Never assume `id-token: write` is sufficient — fork PRs override write permissions to read-only regardless of the workflow `permissions:` block.'
|
|
133
|
+
- 'Add an explicit `if:` guard on every OIDC-dependent step to skip it for fork PRs: `if: github.event.pull_request.head.repo.fork == false`.'
|
|
134
|
+
- 'Use `pull_request_target` only after thoroughly reading the security hardening guide — it runs in base repo context and is vulnerable to pwn requests if fork code is checked out.'
|
|
135
|
+
- 'For attestation workflows, run attestation as a post-merge step on `push` to the default branch, not on PRs from forks.'
|
|
136
|
+
docs:
|
|
137
|
+
- url: 'https://github.com/actions/attest-build-provenance/issues/99'
|
|
138
|
+
label: 'actions/attest-build-provenance#99 — OIDC unavailable in fork PR workflows (open)'
|
|
139
|
+
- url: 'https://github.com/aws-actions/configure-aws-credentials/issues/373'
|
|
140
|
+
label: 'aws-actions/configure-aws-credentials#373 — OIDC fails for pull request from fork'
|
|
141
|
+
- url: 'https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token'
|
|
142
|
+
label: 'GitHub Docs — GITHUB_TOKEN permissions for forked repos'
|
|
143
|
+
- url: 'https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-pull_request_target-safely'
|
|
144
|
+
label: 'GitHub Docs — Security hardening for pull_request_target'
|