GitHub Actions workflow_run works only from the default branch

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_run event, 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_run event of the completed workflow.
  • The Gap: Because the new release.yml file only exists in the feature branch and has not yet been merged into main, 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_run to trigger based on files in any branch, a malicious contributor could submit a Pull Request containing a new .github/workflows/malicious.yml file.
  • 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_TOKEN or 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.yml file (with workflow_run configured) into the main branch without any deployment logic. Once the “listener” is active on the default branch, the feature branch workflows will trigger correctly.
  • Workflow Dispatch: Use workflow_dispatch to 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_dispatch event via an API call from the first workflow. This bypasses the default-branch limitation of workflow_run because 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.

Leave a Comment