Monorepo CI has one recurring chore: every workflow carries a paths: list that someone has to keep in sync with the code.
Add a shared library, forget to add it to three filters, and a breaking change sails through CI untested.
dynamic-monorepo is a GitHub Action that works out which projects a push or pull request affects, including projects that depend on what changed. It reads the manifests you already have (package.json workspaces, go.mod, Cargo.toml, pyproject, pom.xml, Gradle, .csproj, Dockerfile, Helm), so most repos need no config at all!
Example 1: All separate workflows into one
rajadilipkolli/spring-boot-microservices-series-v2 is a Spring Boot microservices showcase with eight services. Each service had its own near-identical workflow and its own paths: filter
jobs:
plan:
runs-on: ubuntu-latest
outputs:
services: ${{ steps.plan.outputs.build }}
has_services: ${{ steps.plan.outputs.has_build }}
steps:
- uses: actions/checkout@v4
- uses: Continuous-Actions/dynamic-monorepo@v1
id: plan
build:
needs: plan
if: needs.plan.outputs.has_services == 'true'
strategy:
matrix:
service: ${{ fromJSON(needs.plan.outputs.services) }}
# ...same build steps as before, written once
The result:
• 9 files changed, +73 / −467 lines.
• Adding a service is one entry in config, not a copied workflow.
• Docs-only changes build nothing.
• A dependency bump in one service builds just that service.
Example 2: Keep your workflows, add a safety net
Not every team wants to restructure CI. uni-helper/create-uni kept its existing per-package workflows.
Running the audit on main found 11 problems across 4 workflows. For example, packages/core depends on the workspace packages @create-uni/shared and @create-uni/config, but its test workflows only watched packages/core/**.
A PR that touched only the shared package never ran the core tests.
• fixed the filters, with 12 lines, all inside on.*.paths
• added a small audit workflow so the filters can't drift again
The audit compares every workflow's paths: list with the dependency graph and flags three problems:
• A workflow watches packages/app/** but not a package app depends on, so a change there skips the workflow.
• A filter lists a bare directory (packages/core instead of packages/core/**), which matches no files.
• A filter references a workflow file that no longer exists.
Findings show up as PR annotations and in the job summary. The audit never fails the build unless you ask it to. This is the whole workflow:
name: Path filter audit
on:
pull_request:
permissions:
contents: read
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Continuous-Actions/dynamic-monorepo@v1
with:
audit: warn # or: fail
Try it!
Option A: replace path filters with a plan job. Copy the plan / build pattern above. Outputs include build, test, deploy, has_build and batched variants for large matrices.
Option B: keep your workflows and add the audit. Drop the audit workflow above into .github/workflows/. To check locally first, run:
npx github:Continuous-Actions/dynamic-monorepo audit
Both options run read-only with permissions: contents: read and need no token. Run npx github:Continuous-Actions/dynamic-monorepo projects to see what it detects in your repo.
AI coding agents can set it up for you too: the repo ships an llms.txt and an Agent Skill.
If it mis-detects something in your repo, open an issue. Real-world repos are how the detection got good.
Top comments (0)