Study guide
2.4 Branch, Tag, and Path Filtering Patterns
GitHub Actions Certification GH-200
Track your progress as you read
Finished sections get a check mark.
1 Chapter 1: Introduction & Exam Overview
1.1 Exam Structure, Format & Scoring 1.2 GitHub Actions Architecture & Core Mental Model 1.3 Hands-On Preparation & Exam Day Strategy
2 Chapter 2: Workflow Triggers & Event Architecture
2.1 Webhook & Code Events: push, pull_request, and pull_request_target 2.2 Scheduled & Manual Triggers: schedule, workflow_dispatch, and repository_dispatch 2.3 Release, Issue, and Dynamic Lifecycle Events 2.4 Branch, Tag, and Path Filtering Patterns
3 Chapter 3: Jobs, Matrix Strategies & Execution Control
3.1 Job Anatomy, Step Execution & Job Dependencies (needs) 3.2 Contexts, Expressions & Functions 3.3 Environment Variables & Workflow Commands 3.4 Matrix Strategies & Multi-Platform Builds 3.5 YAML Anchors, Aliases & Merge Keys
4 Chapter 4: Reusable Workflows & Troubleshooting
4.1 Authoring & Calling Reusable Workflows (workflow_call) 4.2 Comparing Reusable Workflows, Composite Actions & Starter Workflows 4.3 Troubleshooting Workflow Runs & Debug Logging 4.4 Concurrency Controls & Status Check Functions
5 Chapter 5: Authoring Custom Actions
5.1 Action Architecture & action.yml Specification 5.2 Authoring Composite Actions 5.3 Building JavaScript Actions 5.4 Building Docker Container Actions
6 Chapter 6: Action Distribution, Marketplace & Advanced Action Types
6.1 Action Versioning, Semantic Releases & Tagging 6.2 Publishing Actions to GitHub Marketplace 6.3 Service Containers & Network Topology 6.4 actions/github-script & Workflow Tooling
7 Chapter 7: Runner Infrastructure & Self-Hosted Runners
7.1 GitHub-Hosted Runners & Larger Runners 7.2 Self-Hosted Runner Setup, Architecture & Communication 7.3 Self-Hosted Runner Security & Ephemeral Runners 7.4 Runner Groups, Labels & Organization Routing
8 Chapter 8: Enterprise Governance, Policies & Environments
8.1 Enterprise & Organization Action Policies 8.2 Environments, Protection Rules & Deployment Approvals 8.3 Repository Rulesets, Required Status Checks & Governance 8.4 Usage Limits, Billing Multipliers & Usage Metrics
9 Chapter 9: Secrets, Permissions & OIDC Cloud Authentication
9.1 Encrypted Secrets Architecture & Scopes 9.2 GITHUB_TOKEN & Least Privilege Configuration 9.3 OpenID Connect (OIDC) & Workload Identity Federation 9.4 Script Injection Prevention & Secure Workflow Patterns
10 Chapter 10: Supply Chain Hardening & Performance Optimization
10.1 Supply Chain Hardening: SHA Pinning, Dependabot & Artifact Attestations 10.2 Caching Dependencies with `actions/cache` 10.3 Artifacts Management & Job Handoff 10.4 Workflow Performance, Cost Optimization & Architecture Review
2.4 Branch, Tag, and Path Filtering Patterns
Key Takeaways Explain This Topic AI
The branches and branches-ignore filters are mutually exclusive within the same event trigger; attempting to configure both results in a YAML validation error.
Glob pattern matching in GitHub Actions supports * (matches characters except /), ** (recursive path matching including /), ? (single character), and ! (negation).
Filter patterns are evaluated sequentially from top to bottom; a negative pattern prefixed with ! excludes previously matched refs or paths, and defining only negative patterns includes all non-matching targets.
Path filtering (paths and paths-ignore) inspects the modified files in a push or pull request; if all changed files match paths-ignore (or none match paths), the workflow does not run.
Combining required status checks with path filtering can cause pull requests to hang in a pending state when changes do not trigger the required check; solving this requires generic check wrappers or multi-job workflows.
Last updated: August 2026
Branch, Tag, and Path Filtering Patterns
In enterprise repositories and monorepos containing multiple services, running every workflow on every commit is inefficient, slow, and costly. GitHub Actions provides filtering mechanisms to execute workflows only when specific branches, tags, or file paths are modified.
Understanding glob pattern syntax, negation precedence, mutual exclusivity rules, and path filtering edge cases is essential for passing the GH-200 exam and architecting scalable enterprise CI/CD pipelines.
1. Branch and Tag Filtering Mechanics
Branch and tag filters scope workflow execution to specific Git references for push and pull_request events.
Mutual Exclusivity Rule
For a single event, you cannot specify both an inclusion filter and an exclusion filter simultaneously:
You cannot define both branches and branches-ignore for the same event.
You cannot define both tags and tags-ignore for the same event.
You cannot define both paths and paths-ignore for the same event.
Attempting to define both triggers a workflow syntax validation error: # ❌ INVALID SYNTAX: Mutually exclusive keys in the same trigger
on:
push:
branches:
- 'main'
- 'releases/**'
branches-ignore: # ERROR: Cannot use branches and branches-ignore together!
- 'releases/**-alpha' # ✅ VALID SYNTAX: Using negation patterns within branches
on:
push:
branches:
- 'main'
- 'releases/**'
- '!releases/**-alpha' # Correct way to exclude specific branches
- Push vs. Pull Request Branch Filtering Semantics
An important distinction on the GH-200 exam is how branches is evaluated across different event types:
For push events: branches filters against the target branch to which commits were pushed ( github.ref ).
For pull_request events: branches filters against the base branch (target of the PR) , NOT the head/source branch containing the author's commits! on:
pull_request:
branches:
- main # Workflow runs only when PR targets main, regardless of PR source branch name
- 2. Glob Pattern Syntax & Matching Rules
GitHub Actions uses standard globbing syntax for matching branches, tags, and file paths:
Glob Pattern Matching Behavior Example Match Non-Match
* Matches zero or more characters except directory separator / releases/v1 (with releases/* ) releases/v1/patch
** Matches zero or more characters including directory separator / src/api/v1/auth.js (with src/**.js or src/**/auth.js ) tests/auth.js
- ? Matches exactly one character (excluding / ) v1.2 (with v?.? ) v10.20
- + Matches one or more of the preceding pattern (in character classes) v1 (with v[0-9]+ ) v
- [abc] Matches any single character enclosed within the brackets app-a (with app-[abc] ) app-d
- [a-z] Matches any single character within the specified range v1.2.3 (with v[0-9].[0-9].[0-9] ) va.b.c
- ! Negation prefix: Excludes matching patterns from the filter list !docs/** N/A
Important
YAML Quoting Rule for Special Characters: In YAML syntax, unquoted strings starting with * or ! are interpreted as YAML anchors/aliases or custom type tags, resulting in parsing errors. Always enclose glob expressions containing * , ! , ? , or brackets in single or double quotes (e.g., 'releases/**' , '!docs/**' ).
3. Pattern Evaluation Order and Precedence Rules
GitHub Actions evaluates glob patterns in a defined sequential order:
Sequential Top-to-Bottom Evaluation: Patterns are evaluated in the exact order they appear in the YAML list.
Inclusion Followed by Negation: If a target matches a positive pattern, it is marked for inclusion. If a subsequent negative pattern ( ! ) matches the target, it is removed from inclusion.
Negation-Only Lists: If a filter contains only negative patterns ( ! ) , GitHub Actions implicitly treats the filter as matching everything except the negated patterns. For example: on:
push:
paths:
- '!docs/**'
- '!**.md'
This configuration triggers on any push where at least one modified file is NOT inside docs/ and does NOT end with .md .
Case Sensitivity
Branch, tag, and path filtering in GitHub Actions is strictly case-sensitive . A path filter for 'src/**.ts' will not match 'SRC/index.ts' .
4. Path Filtering in Practice & The Required Status Check Dilemma
Path filtering allows workflows to execute only when specific files or directories are modified.
How Path Changes Are Evaluated
For push events, GitHub Actions evaluates all modified, added, or deleted files between github.event.before and github.event.after .
For pull_request events, GitHub Actions evaluates all changed files across the entire pull request diff against the base branch.
Trigger Threshold: If at least one changed file matches the paths filter, the workflow triggers. If all changed files match paths-ignore (or none match paths ), the workflow is skipped. name: Backend API CI
on:
push:
branches: [main]
paths:
- 'services/api/**'
- 'shared/libs/**'
- 'package.json'
- '!services/api/**/*.md'
pull_request:
branches: [main]
paths:
- 'services/api/**'
- 'shared/libs/**'
- The "Required Status Check" Hanging Dilemma
A classic enterprise issue on the GH-200 exam involves Branch Protection Rules combined with path filters :
A repository requires a status check named build-and-test to pass before a pull request can be merged into main .
The workflow that produces build-and-test has paths: ['src/**'] .
A developer opens a pull request that updates only docs/README.md .
The Problem: Because no files under src/** changed, GitHub Actions skips the workflow entirely. As a result, build-and-test never reports a status (neither success nor failure).
The Impact: The pull request displays Expected — Waiting for status to be reported , permanently blocking the developer from merging. +-------------------------------------------------------------------------+
- | REQUIRED STATUS CHECK DEADLOCK |
- | |
- | [Branch Protection Rule] ---> Requires check: 'ci/test' |
- | [Pull Request #42] ---> Modifies ONLY 'docs/architecture.md' |
- | [Workflow paths filter] ---> paths: ['src/**'] |
- | |
- | [Result] ---> Workflow skipped. 'ci/test' is NEVER sent. |
- | [PR State] -> Stalled at: 'Expected — Waiting for status to report' |
- +-------------------------------------------------------------------------+
- Recommended Solutions
Strategy 1: Universal Workflow with Internal Conditional Steps: Remove top-level paths from the on: trigger. Run the workflow on all PRs, but use a path-filtering step (like dorny/paths-filter ) to conditionally skip heavy build steps while still reporting a successful top-level check.
Strategy 2: Status Check Aggregator / Wrapper: Create a lightweight generic job that always runs and reports success when non-code files are changed.