Monorepos, Path Filters, Changed-File Detection, and Selective Pipelines: Configuration, Design Patterns, and Trade-Offs
Choose native filters, custom diffs, dependency-aware selectors, no-op aggregation, scheduled full suites and matrix strategies without making correctness depend on skipping.
Learning objectives
- Choose between native path filters and an in-workflow custom diff based on required-check and dependency needs.
- Compare component-local selection with dependency-aware closure.
- Design a stable no-op/aggregate required check and an independent full-suite lane.
- Choose static versus generated matrices using evidence and maintenance cost.
- Evaluate latency, auditability, portability and failure isolation trade-offs.
1. Design question: where should selection live?
Selective CI has several legitimate implementation points. GitHub can filter an entire workflow before run creation. A selector job can compute changes after a run exists. A static matrix can evaluate per-cell conditions. A generated matrix can create only the required cells. These choices affect branch protection, observability and failure modes differently; there is no universal “best monorepo YAML.”
2. Native path filter versus custom diff
| Choice | Strength | Risk | Best fit |
|---|---|---|---|
Native paths |
No runner cost when unmatched; simple glob rules | Required workflow may disappear/Pending; no dependency closure by itself; platform diff limits | Optional workflows with simple direct ownership. |
| Custom selector job | Full evidence; dependency graph; fallback logic; stable aggregate check | Consumes a small runner job; must maintain Git-history logic | Required CI and dependency-aware monorepos. |
| Hybrid | Native filter for clearly optional lanes + in-workflow selector for required lane | Two rule sets can drift | Large repo with multiple service levels. |
Do not duplicate the same critical ownership logic in many workflow
paths blocks. If component dependencies are nontrivial,
centralize the graph and test it.
3. Component-local versus dependency-aware selection
Component-local selection asks “did files under this component change?” Dependency-aware selection asks “could this component’s behavior change because any dependency changed?” The second question is what correctness requires.
| Change | Local selector | Dependency-aware selector |
|---|---|---|
apps/api/** |
api | api |
apps/web/** |
web | web |
libs/shared/** |
shared only / maybe none | api + web |
| root lockfile/build config | often none | full fallback or all consumers |
| selector graph itself | maybe none | full suite + selector contract tests |
4. Skip workflow versus no-op required check
A required workflow skipped before run creation can leave a Pending check. A job skipped by a condition behaves differently and can report success. The safer required-check pattern is one stable aggregate job inside an always-created PR workflow; that job validates the selector and selected job conclusions.
If zero components are selected, the aggregate records why and succeeds. If selection errors, dependency-contract checks fail, or any selected job fails, it fails.
5. PR incremental suite versus scheduled/manual full suite
PR selection optimizes feedback. A broad full suite verifies the selector’s blind spots and integration assumptions independently. The full lane must not reuse the same buggy selector to decide what “all” means; it should enumerate the authoritative component inventory directly.
A scheduled run is not a substitute for PR protection because it happens later. It is a detection backstop. Preserve the failing selector version and change set when it exposes under-selection.
6. Static matrix versus generated matrix
| Pattern | Pros | Cons | Use when |
|---|---|---|---|
Static matrix + cell if |
Inventory visible in YAML | Verbose skip logic | Small stable component count. |
| Generated matrix | Creates only selected jobs; scalable | JSON/output contract becomes critical | Medium/large repo with central selector. |
| Reusable per-component workflow | Strong interface and ownership | Cross-workflow aggregation/versioning complexity | Platformized delivery, Chapter 29. |
7. Renames and deletions require conservative semantics
A rename can cross ownership boundaries, and deletion of a shared
file can break dependents. The lab avoids ambiguity with
--no-renames, causing old and new paths to appear as
deletion/addition. A more advanced selector may parse
--name-status -z and explicitly evaluate both paths.
Do not infer “deleted means no test needed.” Deletion is still a change to the component graph.
8. Required check naming is an API contract
Branch protection and rulesets identify required checks by their reported names/source. Renaming a workflow or job can therefore affect merge governance. Keep the aggregate job name stable and document it as part of the monorepo CI interface.
9. Security, runner and cost trade-offs
| Dimension | Selective design implication |
|---|---|
| Security | Untrusted PR paths are data; selector and tests use minimal permissions on hosted runners. |
| Latency | Selector should be much cheaper than the work it saves. |
| Cost | Native optional filters save all runner minutes; in-workflow selector spends one small job to preserve governance. |
| Auditability | Retain revision pair, changed paths, mapping reasons and matrix decisions. |
| Portability | Keep component graph in repository source rather than relying only on hosted-service UI. |
| Failure isolation |
Matrix separates component failures;
fail-fast: false preserves broader evidence.
|
10. Worked selection scenarios
| Repository | Choice | Prerequisites | Observable proof |
|---|---|---|---|
| 2 apps, no shared code | Static matrix or simple path rules | Stable direct ownership | per-component checks + stable aggregate. |
| 20 services + shared libraries | Custom dependency-aware generated matrix | Versioned graph, adequate Git history | selection.json + matrix cells + full-suite contract test. |
| Docs preview only | Native paths |
Not sole required gate | absence of optional workflow is acceptable. |
| High-risk release branch | PR selective + pre-release full suite | Authoritative component inventory | full-run evidence before promotion. |
| Unknown build-system file changed | Full fallback | Conservative policy | selector records fallback reason and selects all. |
11. Version-sensitive assumptions — September 10, 2026
Current GitHub.com documentation states that pull-request path filtering uses three-dot diffs, push filtering uses two-dot diffs, a push with more than 1,000 commits or a diff timeout runs rather than filters, and path evaluation examines up to the first 3,000 changed files. A workflow skipped by path/branch filtering or skip annotation can leave a required check Pending. Verify these service values again during maintenance.
12. Design summary
Use the simplest selector that can prove correctness. Native filters are excellent when a workflow is optional and ownership is direct; dependency-aware in-workflow selection is better for required monorepo CI. Keep one stable governance-facing check and an independent full-run policy.
Knowledge check
When is native paths especially
appropriate?
For optional workflows with simple direct ownership where it is acceptable that no workflow run exists when paths do not match.
Why should a scheduled full suite enumerate components independently?
If it calls the same flawed selector, it cannot reliably detect under-selection defects in that selector.
What should happen when a root build configuration file changes?
Usually fan out broadly or use a full fallback unless the selector can prove a narrower dependency set.
Why treat the aggregate job name as a contract?
Branch protection/rulesets can require that check name, so changing it can change governance behavior.
What is the safer rename default?
Evaluate both old and new ownership/dependents, or disable rename detection so the move appears as delete + add.
Official references and version notes
- Workflow syntax: path filters — Current paths/paths-ignore matching, two-dot versus three-dot diff rules, pattern ordering and GitHub.com diff limits.
- Troubleshooting workflows — Current path-filter diff limits and workflow-run troubleshooting.
- Troubleshooting required status checks — Why a path-filtered workflow can leave a required check Pending and how skipped jobs differ.
- Matrix jobs — Dynamic matrix concepts and per-cell jobs.
- Passing information between jobs — Selector job outputs used by downstream matrix and aggregate jobs.
- Events that trigger workflows — Event-specific base/head/ref semantics.
- actions/checkout — Current checkout behavior; v7.0.1 is pinned by full commit SHA in executable examples.
- Git diff documentation — Two-dot/three-dot revision comparison and NUL-safe path output options.
- Git merge-base — Computing the common ancestor for dependency-aware pull-request change detection.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.