Run independent GitHub Actions jobs in parallel
Two configuration shapes turn parallel work sequential without any real dependency requiring it: a second workflow gated behind workflow_run when nothing in it needs the first workflow's result, and a matrix strategy throttled with max-parallel: 1 so every variant waits for the one before it.
ci.parallel.independent-jobsstatic · checkable from the repoAI agents open the PR
StarSling agents run this exact audit on your workflows, apply the fix, and open a reviewable PR automatically.
Do this
workflow_run and max-parallel exist for real cases: a deploy that must follow a successful build, or a shared resource a matrix cannot hit concurrently. The problem is when either is applied where nothing forces it. A workflow_run chain adds a full second workflow dispatch on top of the wait - GitHub has to detect the completion event, evaluate the trigger, and queue a fresh run - so the two workflows do not just run one after another, they run one after another plus that queue latency, for work that could have been two jobs in one job graph finishing at the same time. A max-parallel: 1 matrix is worse per variant: a matrix exists specifically to fan work out, and pinning it to one turns that fan-out into a for-loop, so the wall-clock cost of the slowest single variant becomes the cost of every variant added together.
name: CI
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm testAvoid this
name: Build
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run buildHow to detect it
Search for workflow_run triggers:
grep -rln 'workflow_run' .github/workflows/.For each match, read the workflow it names in
workflows:and open the upstream workflow it runs after. Check whether the triggered workflow reads anything the first workflow produced (build artifacts, a version bump, a tag) via actions/download-artifact, a checkout of a ref the first workflow created, or an output the first workflow set.If nothing is read across the boundary, the chain is a serialization artifact, not a real dependency, and the workflows are a candidate to merge into one workflow with a job graph.
Search for throttled matrices:
grep -rn 'max-parallel' .github/workflows/.For each hit, read the job's matrix and the steps that run inside it. Look for a resource the steps genuinely share and cannot use concurrently: a single deployment target, a rate-limited external API key, one database the job writes to. If nothing in the steps references a shared, non-concurrent-safe resource, the throttle has no matching justification in the workflow.
Tradeoffs and safety
workflow_run is the right tool when the second workflow truly needs the first one's output - a signed artifact, a version it bumped, a status only computable after the build finishes. Consolidating those into one workflow is correct too, using needs: between jobs (see the sibling recipe on spurious needs:), but if they must stay separate workflows (different triggers, different permission scopes, a reusable workflow shared across repos), keep workflow_run and document what it is waiting for.
max-parallel: 1 is sometimes the honest answer to a real constraint: a matrix that all deploy to the same staging environment, a matrix that all call a third-party API with a strict per-second rate limit, or a matrix that all write to one shared test database. In those cases the fix is not removing the throttle, it is narrowing it - move the shared resource into its own serialized job or a concurrency group scoped to that resource, and let everything else in the matrix run in parallel.
Raising max-parallel above 1 without checking for a shared resource can produce flaky failures that look unrelated to the change (two matrix legs racing to write the same file, deploy to the same slot, or exhaust a shared rate limit). Increase it, watch several runs, and check for exactly that failure signature before calling it done.
Merging two workflows into one changes their trigger surface: a workflow_run consumer often runs with different permissions or a different trigger (e.g. it only fires after a push-triggered workflow succeeds, never on pull_request). Confirm the merged workflow's triggers and permissions still match what each half needed before deleting the original files.
Verify it worked
Hand this prompt to your coding agent (Claude Code, Cursor, and the like) to run this audit and open the fix as a reviewable PR.
Audit this repository's GitHub Actions workflows for serialization that has no matching dependency and fix what you find.
1. Run `grep -rln 'workflow_run' .github/workflows/`. For each workflow using workflow_run,
open the workflow it names and check whether it reads anything the triggering workflow
produced (artifacts via actions/download-artifact, a ref or tag the first workflow created,
an output it set). If it reads nothing across that boundary, propose merging the two
workflows into one with separate parallel jobs instead of a workflow_run chain. If it
does read something, leave it - it is a real dependency, not a serialization bug.
2. Run `grep -rn 'max-parallel' .github/workflows/`. For each match, read the matrix and
the steps inside the job. Identify whether the steps share a resource that cannot be
hit concurrently (single deploy target, rate-limited API key, one shared database).
If you find no such resource, propose removing max-parallel or raising it. If you do
find one, do not remove the throttle - instead propose isolating the shared-resource
step into its own serialized job or a concurrency group scoped to that resource, so the
rest of the matrix can still run in parallel.
3. Read the GitHub Actions docs on workflow_run, reusable workflows, and matrix strategy
linked on this page before making changes.
4. Show the full diff and open a pull request; do not apply changes blindly. In the PR
body, name each workflow or matrix changed, state what dependency check you did (what
you confirmed was or was not read across the workflow_run boundary, or which shared
resource you did or did not find), and state how to verify: compare the run's wall-clock
time and job start times before and after.Confirm the change landed
For a merged workflow_run chain, push a commit and watch the Actions run: the jobs that used to be two separate workflow runs should now appear as parallel jobs inside one run, starting at the same time rather than one waiting on the other's completion event.
For a raised or removed max-parallel, open the run's Actions UI and confirm the matrix's variants show overlapping start times instead of a strict start-after-previous-finishes order.
Compare the total wall-clock time of the run before and after: a merged workflow should drop by roughly the amount that was previously spent waiting on the workflow_run completion event and re-dispatch, and a de-throttled matrix should drop toward the duration of its single slowest variant instead of the sum of all variants.
Go further
One fix, all of them, or forever.
You have the prompt for this one practice. Here is how much further you can take it, each step doing more for you than the last.
Fix this one thing
Copy the prompt above
Hand OPT22 / OPT23 to your coding agent and fix it in your repo today.
Fix everything, once
Install the ci-speedup skill
One prompt audits your whole repo against all 73 ci-speedup patterns (these 2 plus 71 more) and hands your agent every fix at once. Open source, MIT, runs locally.
Keep it fixed, forever
Install the StarSling GitHub App
Connect GitHub and the fixes stay applied as your CI evolves, with agents that keep inspecting your workflows and opening optimization PRs you review.
Sources
1GitHub Actions: events that trigger workflows (workflow_run) (opens in new tab)
2GitHub Actions: reusing workflows (workflow_call) (opens in new tab)
3GitHub Actions: running variations in a job matrix (opens in new tab)
Last updated 2026-08-21