Summary
A critical failure occurred in our CI/CD pipeline during a refactor where a downstream release workflow failed to trigger following the successful completion of a validation workflow. While the validation workflow ran perfectly on a feature branch, the subsequent release workflow, which relied on the workflow_run event, remained completely dormant. This resulted in a broken deployment pipeline for all non-main branch activities.
Root Cause
The issue stems from a fundamental security and architectural constraint in GitHub Actions: workflow_run triggers only look at the default branch (usually main or master) to find the workflow definition.
- Event Scoping: When a workflow uses the
workflow_runevent, GitHub does not look at the code within the branch that triggered the first workflow. - Configuration Source: GitHub searches the default branch to determine which workflows are configured to listen for the
workflow_runevent of the completed workflow. - The Gap: Because the new
release.ymlfile only exists in the feature branch and has not yet been merged intomain, the GitHub Actions engine has no record of a listener for the “Run build and unit tests” event on the default branch.
Why This Happens in Real Systems
This behavior is a security feature designed to prevent unauthorized code execution.
- Malicious Injection: If GitHub allowed
workflow_runto trigger based on files in any branch, a malicious contributor could submit a Pull Request containing a new.github/workflows/malicious.ymlfile. - Privilege Escalation: This malicious workflow could listen for the completion of a legitimate, high-privilege workflow (like a production deployment) and immediately execute code with the
GITHUB_TOKENor other secrets context, effectively bypassing branch protection rules. - Consistency: By forcing the “listener” workflow to exist on the default branch, GitHub ensures that only vetted, merged code can define the orchestration logic of the repository.
Real-World Impact
- Broken CI/CD Loops: Developers lose confidence in the pipeline when “success” in one stage does not lead to the expected next stage.
- Deployment Delays: Automated release processes are halted, requiring manual intervention to trigger workflows or merge code prematurely.
- False Positives: In some monitoring setups, the absence of a second workflow might not be flagged as an “error,” but rather as a “skipped” process, leading to silent failures in the delivery lifecycle.
Example or Code
To fix this, you must merge the listener configuration into the default branch first. However, to test the logic within a feature branch, you should temporarily switch to the push event or use a manual trigger.
# release.yml (Modified for testing on feature branches)
name: Release builds to Google Play Console
on:
# workflow_run will NOT work on branches until merged to main
# Use push for testing the logic in a PR/Feature branch
push:
branches:
- "**"
# Allows manual testing via the Actions tab
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy
run: echo "Deploying to Play Store..."
How Senior Engineers Fix It
Senior engineers solve this by decoupling logic execution from orchestration triggers or by following a strict promotion workflow.
- Two-Step Merge: First, merge the
release.ymlfile (withworkflow_runconfigured) into themainbranch without any deployment logic. Once the “listener” is active on the default branch, the feature branch workflows will trigger correctly. - Workflow Dispatch: Use
workflow_dispatchto allow manual triggering of the second workflow during the testing phase to verify the deployment steps. - Repository Dispatch: In highly complex environments, use a
repository_dispatchevent via an API call from the first workflow. This bypasses the default-branch limitation ofworkflow_runbecause it is an explicit API event. - Composite Actions: Instead of relying on event chaining, move the heavy lifting into Reusable Workflows or Composite Actions that are called directly by a single orchestrator.
Why Juniors Miss It
- Assumption of Branch Parity: Juniors often assume that the GitHub Actions engine treats all branches as identical execution environments.
- Focus on Syntax vs. Platform Logic: They spend time debugging the
if: ${{ github.event.workflow_run.conclusion == 'success' }}syntax rather than investigating the GitHub platform’s event-routing architecture. - Lack of Security Context: They view the behavior as a “bug” or a “limitation” rather than a deliberate security boundary designed to prevent code injection via Pull Requests.