@htekdev/actions-debugger 1.0.123 → 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.
Files changed (32) hide show
  1. package/errors/caching-artifacts/caching-artifacts-073.yml +100 -0
  2. package/errors/caching-artifacts/caching-artifacts-074.yml +117 -0
  3. package/errors/known-unsolved/known-unsolved-070.yml +83 -0
  4. package/errors/known-unsolved/known-unsolved-071.yml +122 -0
  5. package/errors/known-unsolved/known-unsolved-072.yml +143 -0
  6. package/errors/permissions-auth/permissions-auth-071.yml +144 -0
  7. package/errors/permissions-auth/permissions-auth-072.yml +112 -0
  8. package/errors/permissions-auth/permissions-auth-073.yml +127 -0
  9. package/errors/permissions-auth/permissions-auth-074.yml +106 -0
  10. package/errors/permissions-auth/permissions-auth-075.yml +137 -0
  11. package/errors/runner-environment/runner-environment-224.yml +74 -0
  12. package/errors/runner-environment/runner-environment-225.yml +85 -0
  13. package/errors/runner-environment/runner-environment-226.yml +91 -0
  14. package/errors/runner-environment/runner-environment-227.yml +106 -0
  15. package/errors/runner-environment/runner-environment-228.yml +117 -0
  16. package/errors/runner-environment/runner-environment-229.yml +119 -0
  17. package/errors/runner-environment/runner-environment-230.yml +129 -0
  18. package/errors/runner-environment/runner-environment-231.yml +90 -0
  19. package/errors/runner-environment/runner-environment-232.yml +131 -0
  20. package/errors/runner-environment/runner-environment-233.yml +90 -0
  21. package/errors/runner-environment/runner-environment-234.yml +114 -0
  22. package/errors/runner-environment/runner-environment-235.yml +151 -0
  23. package/errors/silent-failures/silent-failures-112.yml +97 -0
  24. package/errors/silent-failures/silent-failures-113.yml +110 -0
  25. package/errors/silent-failures/silent-failures-114.yml +116 -0
  26. package/errors/silent-failures/silent-failures-115.yml +130 -0
  27. package/errors/silent-failures/silent-failures-116.yml +117 -0
  28. package/errors/silent-failures/silent-failures-117.yml +137 -0
  29. package/errors/silent-failures/silent-failures-118.yml +156 -0
  30. package/errors/yaml-syntax/yaml-syntax-075.yml +128 -0
  31. package/errors/yaml-syntax/yaml-syntax-076.yml +107 -0
  32. 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,83 @@
1
+ id: known-unsolved-070
2
+ title: 'ubuntu-24.04 image 20260513.135 breaks WireMock-Spring dynamic port binding in @ConfigurationProperties tests'
3
+ category: known-unsolved
4
+ severity: limitation
5
+ tags:
6
+ - ubuntu
7
+ - ubuntu-24.04
8
+ - spring-boot
9
+ - wiremock
10
+ - configurationproperties
11
+ - regression
12
+ - java
13
+ - test
14
+ patterns:
15
+ - regex: 'foo\.bar\.service\.url=http://localhost:\d+'
16
+ flags: i
17
+ - regex: 'http://localhost:9008'
18
+ flags: i
19
+ - regex: '@ConfigurationProperties'
20
+ flags: i
21
+ - regex: 'wiremock-spring-boot'
22
+ flags: i
23
+ error_messages:
24
+ - 'foo.bar.service.url=http://localhost:<random>'
25
+ - 'The @ConfigurationProperties(prefix = "foo.bar.service") bean ends up bound to the static fallback URL from application-integration-test.yml (http://localhost:9008).'
26
+ - 'The HTTP client then hits localhost:9008 and the test times out.'
27
+ root_cause: |
28
+ Starting with Ubuntu 24.04 image `20260513.135`, some Spring Boot integration tests that rely on
29
+ `wiremock-spring-boot` dynamic port injection regress in GitHub Actions even though the same commit,
30
+ same JDK, and same test pass on `ubuntu-22.04` and on the older Ubuntu 24.04 image `20260413.86`.
31
+
32
+ The observed behavior is:
33
+ 1. WireMock starts and publishes a dynamic property such as `foo.bar.service.url=http://localhost:<port>`.
34
+ 2. The property is visible in logs before the Spring banner.
35
+ 3. The `@ConfigurationProperties` bean still binds to the static fallback value from YAML
36
+ (`http://localhost:9008`).
37
+ 4. The client calls the fallback URL and the test times out.
38
+
39
+ The exact platform-level trigger inside the updated runner image has not been isolated yet. The issue
40
+ is deterministic on the affected image family, reproducible across multiple repositories, and still open.
41
+ At the time of writing there is no confirmed runner-side fix or single root cause from GitHub.
42
+ fix: |
43
+ There is no confirmed upstream fix yet.
44
+
45
+ Current workarounds:
46
+ - pin the workflow to `ubuntu-22.04` while the Ubuntu 24.04 regression is investigated
47
+ - avoid test setups that rely on `@ConfigurationProperties` picking up a dynamically injected URL
48
+ - pass the WireMock URL through `@DynamicPropertySource`, a test-specific bean, or direct
49
+ `Environment` reads closer to the HTTP client wiring
50
+ fix_code:
51
+ - language: yaml
52
+ label: 'Pin the test job to Ubuntu 22.04 until the regression is fixed'
53
+ code: |
54
+ jobs:
55
+ integration-tests:
56
+ runs-on: ubuntu-22.04
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+ - uses: actions/setup-java@v4
60
+ with:
61
+ distribution: temurin
62
+ java-version: '21'
63
+ - name: Run integration tests
64
+ run: ./mvnw test
65
+
66
+ - language: java
67
+ label: 'Inject the dynamic WireMock URL through DynamicPropertySource'
68
+ code: |
69
+ @DynamicPropertySource
70
+ static void registerWireMockProperties(DynamicPropertyRegistry registry) {
71
+ registry.add("foo.bar.service.url", wireMockServer::baseUrl);
72
+ }
73
+ prevention:
74
+ - 'Treat Ubuntu image rollouts as a possible source of Java integration-test regressions, even when your dependencies are unchanged.'
75
+ - 'Prefer explicit test-time property injection over fallback YAML values when the URL must come from a dynamically started mock server.'
76
+ - 'Keep a side-by-side `ubuntu-22.04` control job available when debugging new `ubuntu-latest` regressions.'
77
+ docs:
78
+ - url: 'https://github.com/actions/runner-images/issues/14136'
79
+ label: 'actions/runner-images#14136 — ubuntu-24.04 image 20260513.135 breaks WireMock-Spring dynamic ports'
80
+ - url: 'https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/ctx-management/dynamic-property-sources.html'
81
+ label: 'Spring Docs — @DynamicPropertySource'
82
+ - url: 'https://github.com/wiremock/wiremock-spring-boot'
83
+ label: 'wiremock-spring-boot project'
@@ -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'