2.1 Webhook & Code Events: push, pull_request, and pull_request_target

Key Takeaways

  • The push event triggers on Git ref updates (branches, tags) and can be scoped using branches, tags, and paths filters.
  • The pull_request event triggers by default only on opened, synchronize, and reopened activity types; other activity types like closed, labeled, or ready_for_review require explicit configuration.
  • Workflows triggered by pull_request execute against the ephemeral merge commit ref refs/pull/NUMBER/merge, running in the fork's context with a read-only GITHUB_TOKEN and zero access to base repository secrets.
  • The pull_request_target event executes in the context of the base repository (target branch) with access to repository secrets and write permissions, evaluating the workflow file from the base branch rather than the PR head branch.
  • Combining pull_request_target with an explicit checkout of untrusted PR code (ref: ${{ github.event.pull_request.head.sha }}) introduces severe security vulnerabilities (pwn-requests) if untrusted build scripts or actions are executed.
Last updated: August 2026

Webhook & Code Events: push, pull_request, and pull_request_target

Every automated pipeline in GitHub Actions begins with an event trigger. GitHub Actions is event-driven by design: an event is a specific activity that triggers a workflow run. In software development lifecycles, the most frequent triggers are code lifecycle events—specifically when commits are pushed directly to a repository branch or submitted through a pull request.

Understanding the subtle execution contexts, Git reference structures, and security boundaries between push, pull_request, and pull_request_target is one of the highest-weight competency areas in the GitHub Actions Certification (GH-200) examination.


1. Deep Mechanics of the push Event

The push event occurs whenever one or more commits are pushed to a repository branch or when a Git tag is pushed. It represents direct code changes landing in the repository.

on:
  push:
    branches:
      - main
      - 'releases/**'
    tags:
      - 'v[0-9]+.[0-9]+.[0-9]+'
    paths:
      - 'src/**'
      - 'package.json'

Key Characteristics and Context Values

  • Triggering Ref (github.ref): For branch pushes, github.ref is formatted as refs/heads/<branch_name> (e.g., refs/heads/main). For tag pushes, it resolves to refs/tags/<tag_name> (e.g., refs/tags/v1.0.0).
  • Target Commit SHA (github.sha): For branch pushes, github.sha is the commit SHA of the tip of the pushed branch. For tag pushes, it is the commit SHA to which the tag points.
  • Commit Comparison Payload: The event payload provides github.event.before (the commit SHA prior to the push) and github.event.after (the commit SHA after the push), allowing workflows to compute precise diff ranges.
  • Default Checkout Behavior: When using actions/checkout@v4, the action automatically checks out github.ref at github.sha, representing the exact state of the pushed branch or tag.

2. The pull_request Event Architecture

The pull_request event triggers on activity related to pull requests. Unlike push, a pull request represents proposed changes from a head branch (which may reside in the same repository or in an external fork) against a base branch.

Default Activity Types vs. Explicit Types

By default, if you specify on: pull_request without defining types, GitHub Actions triggers the workflow for only three activity types:

  • opened: A new pull request is created.
  • synchronize: New commits are pushed to the head branch of the pull request.
  • reopened: A previously closed pull request is reopened.

If you need a workflow to respond to other pull request lifecycle events, you must explicitly declare all desired activity types in the types array. Specifying types overrides the defaults completely:

Activity TypeDescriptionCommon CI/CD Use Case
openedPull request openedInitial lint, unit tests, security scanning
synchronizeNew commit pushed to PR branchRe-running test suites on updated code
reopenedClosed PR reopenedRe-validating baseline checks
closedPR merged or closed without mergingTriggering post-merge cleanup, closing test environments
labeled / unlabeledLabel added or removedAutomated routing, triage, triggering stage deployments
ready_for_reviewDraft PR marked ready for reviewRunning resource-heavy integration test suites
converted_to_draftPR converted back to draft statusPausing continuous review automations
assigned / unassignedReviewer or assignee updatedTeam notification webhooks
editedPR title or description editedEnforcing Conventional Commits / PR title linters

[!IMPORTANT] When handling the closed activity type, always inspect the boolean context variable ${{ github.event.pull_request.merged }}. If github.event.pull_request.merged == true, the PR was merged into the base branch; if false, the PR was closed without being merged.

on:
  pull_request:
    types: [opened, synchronize, reopened, closed, labeled]
    branches:
      - main

jobs:
  deploy-cleanup:
    if: github.event.action == 'closed' && github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    steps:
      - run: echo "PR #${{ github.event.pull_request.number }} merged into main! Cleaning up preview env..."

Ref Resolution: The Ephemeral Merge Commit

When a pull_request event triggers, GitHub creates a synthetic, ephemeral test merge commit.

  • Merge Ref: github.ref points to refs/pull/<pr_number>/merge.
  • Head Ref: refs/pull/<pr_number>/head points to the tip of the PR branch.

When actions/checkout@v4 runs in a pull_request workflow, it defaults to checking out refs/pull/<pr_number>/merge. This ensures that your CI pipeline tests the proposed changes as if they were already merged into the current base branch, catching integration regressions and merge conflicts before the pull request is approved.

[!WARNING] If merge conflicts exist between the PR head branch and the base branch, GitHub cannot generate the synthetic refs/pull/<pr_number>/merge ref. In such cases, the workflow run will fail at checkout or may fail to trigger until conflicts are resolved.

Loading diagram...
Execution Context and Security Boundary: pull_request vs pull_request_target

3. The Security Boundary: pull_request vs pull_request_target

In enterprise and open-source workflows, managing pull requests from external forks requires a strict balance between automation convenience and security isolation.

The pull_request Fork Security Model

When a workflow is triggered by pull_request from a forked repository:

  1. Workflow Definition Source: The workflow file is read from the head branch (the fork).
  2. GITHUB_TOKEN Permissions: The automatic token is scoped to read-only (contents: read), regardless of repository default settings.
  3. Secrets Access: The workflow has zero access to repository secrets. ${{ secrets.* }} resolves to empty strings.
  4. Runner Security: Protects the upstream repository from malicious code execution and credential exfiltration.

However, this isolation creates challenges for legitimate automations that need to label PRs, post bot comments, or execute SonarQube / external service analysis requiring API keys.

The pull_request_target Event

To solve the credential isolation problem, GitHub introduced pull_request_target. This event triggers on pull request activities but alters the execution environment fundamentally:

  1. Workflow Definition Source: The workflow file is loaded strictly from the base repository target branch (e.g., main). Changes made to .github/workflows/ inside the PR are completely ignored.
  2. Execution Context: github.ref resolves to the base branch (e.g., refs/heads/main).
  3. Token & Secrets: The workflow receives the base repository GITHUB_TOKEN (with standard write permissions) and has full access to repository and organization secrets.

The "Pwn-Request" Attack Vector and Safe Usage Rules

Because pull_request_target runs with write permissions and secret access, checking out untrusted code from the fork PR head branch is extremely dangerous.

# ❌ INSECURE ANTI-PATTERN: PWN-REQUEST VULNERABILITY
on:
  pull_request_target:

jobs:
  unsafe-build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout untrusted fork code into privileged runner
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }} # DANGER!
      - name: Run build script
        run: |
          npm install
          npm test # Attacker injects malicious postinstall script to steal secrets!
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
          AWS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID }}

[!CAUTION] Pwn-Request Exploitation: If you checkout the PR head commit (github.event.pull_request.head.sha) in a pull_request_target workflow and execute any build step, test command, linter, or action from that checked-out code, an external attacker can submit a pull request containing malicious scripts (e.g., inside package.json scripts or custom code) that execute inside your privileged environment and exfiltrate all secrets.

Recommended Safe Pattern for pull_request_target

Only use pull_request_target for lightweight administrative tasks (such as labeling, checking PR metadata, or posting comments) where untrusted code is never checked out or executed:

# ✅ SECURE PATTERN: Metadata handling without checking out untrusted code
name: Label Pull Request
on:
  pull_request_target:
    types: [opened, labeled]

permissions:
  pull-requests: write
  contents: read

jobs:
  auto-label:
    runs-on: ubuntu-latest
    steps:
      - name: Add Triage Label
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.addLabels({
              issue_number: context.payload.pull_request.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              labels: ['needs-review']
            });
Featurepull_requestpull_request_target
Workflow Code SourcePR Head / Fork BranchBase Branch (main)
Checked-out Ref (Default)refs/pull/:id/mergerefs/heads/<base>
Fork Secrets AccessNone (Disabled)Full Target Repo Secrets
GITHUB_TOKEN ScopeRead-Only (on forks)Read/Write (configurable)
Primary Use CaseBuilding & Testing CodePR Labeling, Triage, Commenting
Test Your Knowledge

An engineer needs a workflow to run automated linting and tests on every commit pushed to a pull request, but also wants to execute a cleanup job whenever the pull request is merged into the default branch. How should the pull_request event be configured in the workflow?

A
B
C
D
Test Your Knowledge

A public open-source repository maintains a workflow triggered by pull_request_target to run CI tests. The workflow checks out the code using actions/checkout@v4 with ref: ${{ github.event.pull_request.head.sha }} and executes npm test. What critical security vulnerability does this configuration introduce?

A
B
C
D
Test Your Knowledge

What Git reference does GitHub Actions check out by default when using actions/checkout@v4 in a workflow triggered by the pull_request event?

A
B
C
D