Orchestrating Cross-Repository Pull Requests via GitHub Actions: Advanced Automation Strategies

Automating the creation of pull requests (PRs) within GitHub Actions workflows is a critical capability for modern DevOps and continuous integration pipelines. While standard workflows often handle builds and tests within a single repository, advanced scenarios require interacting with external repositories, managing fine-grained permissions, and automating the review process. These operations demand a shift beyond the default GITHUB_TOKEN, which is limited to the repository where the workflow is running. Achieving cross-repository automation, particularly for tasks such as updating Homebrew taps or synchronizing configuration across multiple codebases, requires sophisticated token management, specific API interactions, and carefully structured workflow configurations. This analysis explores the architectural patterns for creating automated pull requests, ranging from GitHub CLI utilities to REST API calls using temporary, scoped access tokens.

The Limitation of Default Tokens and the Need for Scoped Access

In a standard GitHub Actions environment, the GITHUB_TOKEN is provided automatically by the platform. However, this token has significant limitations. It is only valid for the repository in which the workflow is running and possesses limited permissions. When a workflow needs to push branches or create pull requests in a different repository, such as moving a generated Homebrew formula from a source repository to a separate tap repository, the default token is insufficient. To bridge this gap, automation strategies must employ temporary, fine-grained access tokens that are generated at runtime. These tokens allow for specific permissions, such as writing to contents or managing pull requests, across different repositories owned by the same organization or user.

The use of personal access tokens (PATs) stored as secrets is a common but suboptimal approach. PATs often have broad permissions and lack the granularity required for secure, automated operations. A more robust solution involves using GitHub Apps that can issue temporary tokens during a workflow run. These tokens are short-lived and can be configured with precise permissions, reducing the security risk associated with long-lived credentials. For instance, the qoomon/actions--access-token action provides a mechanism to generate these temporary tokens, enabling workflows to clone repositories, create branches, and open pull requests in target repositories with the necessary authority.

Configuring Global and Local Token Policies

Implementing temporary token generation requires a structured policy configuration to define which workflows can request tokens and what permissions they are granted. This configuration operates on two levels: global policies and local policies. The global policy is defined in a dedicated repository, typically named .github-access-token. Within this repository, an access-token.yaml file establishes the overarching rules, specifying which repositories are allowed to request tokens and the general permissions associated with those requests. This acts as an organization-wide or user-wide master policy, ensuring that only authorized workflows can generate access tokens.

Local policies are configured within each target repository that will receive the automated changes. A file named .github/access-token.yaml must be present in the target repository. This local configuration must match the owner of the global repository and serves as a per-repository confirmation. It narrows down the rules defined in the global file, explicitly allowing specific actions within that particular repository. This two-tiered approach ensures that a workflow in one repository cannot arbitrarily push changes or create pull requests in another without explicit permission from both the global policy and the local repository configuration. For example, in a setup involving three repositories owned by the same user, the global policy in .github-access-token defines the allowed actions, the source repository mkdotenv contains the workflow that generates the formula, and the target repository homebrew-mkdotenv contains the local policy that accepts the automated pull requests.

Generating Temporary Tokens and Cloning Repositories

Once the policies are in place, the workflow can generate a temporary token with the necessary permissions. This is typically done using a specific action, such as qoomon/actions--access-token. The action is configured with the target repository and the required permissions, such as contents: write and pull_requests: write. The generated token is then exposed as an output from the action step and can be passed to subsequent steps as an environment variable. This token is used to authenticate Git operations, allowing the workflow to clone the target repository securely.

The cloning process involves injecting the token into the HTTPS URL to enable non-interactive authentication. This allows the workflow to push new branches and commit changes without requiring interactive input. For example, the token can be embedded in the clone URL as x-access-token:${GH_PAT}, where GH_PAT is the environment variable containing the temporary token. This method ensures that the Git operations are authenticated correctly and that the workflow has the necessary permissions to modify the repository contents. Additionally, standard Git configuration steps, such as setting the user name and email, are required to ensure that commits are properly attributed to the GitHub Actions bot.

```yaml
- name: Generate GitHub App token
id: token
uses: qoomon/actions--access-token@v3
with:
repository: pc-magas/homebrew-mkdotenv
permissions: |
contents: write
pull_requests: write

  • name: setup git and clone brew formula
    env:
    GHPAT: ${{ steps.token.outputs.token }}
    run: |
    git config --global user.name "github-actions"
    git config --global user.email "[email protected]"
    git clone https://x-access-token:${GH
    PAT}@github.com/pc-magas/homebrew-mkdotenv.git
    cd homebrew-mkdotenv
    git checkout -b test-update-formula-${{ github.run_number }}
    ```

Creating Pull Requests via the GitHub REST API

After the changes are committed to a new branch in the target repository, the workflow must create a pull request. This can be achieved using the GitHub REST API, which provides a programmatic way to manage pull requests. The curl command is commonly used to make these API calls within GitHub Actions workflows. The request must include the necessary headers, such as the authorization bearer token, the content type, and the API version. The body of the request specifies the title, head branch, base branch, and body of the pull request.

Using curl for API consumption offers precise control over the request and allows for error handling. The --fail-with-body argument is particularly useful in this context. It ensures that if the API returns an error HTTP status code, such as 4XX or 5XX, the step fails and the error response body is displayed. This provides immediate feedback on whether the pull request was created successfully or if there was an issue with the request. This level of detail is crucial for debugging and ensuring the reliability of automated workflows.

bash curl --fail-with-body -X POST https://api.github.com/repos/pc-magas/homebrew-mkdotenv/pulls \ -H "Authorization: Bearer $GH_PAT" \ -H "Accept: application/vnd.github+json" \ -H "Content-Type: application/json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ -d "{\"title\": \"Update formula [VERSION $VERSION]\",\"head\": \"$BRANCH_NAME\",\"base\": \"master\",\"body\": \"Automated update\"}"

Automating Pull Requests with the GitHub CLI

An alternative to using the REST API directly is to utilize the GitHub CLI (gh) tool. The gh pr create command provides a streamlined way to create pull requests with various options, such as specifying the reviewer, adding labels, and marking the PR as a draft. This approach is particularly useful when the workflow is triggered by specific events, such as the creation of a staging branch. The workflow can watch for the creation of the branch and automatically open a pull request against a destination branch, such as main.

The GitHub CLI workflow requires the GITHUB_TOKEN to be available in the environment, which is provided by default in GitHub Actions. The workflow can be configured to run only when a specific branch is created, using conditional statements. This allows for fine-grained control over when pull requests are generated. For example, a workflow can be set up to create a pull request from the staging branch to the main branch whenever the staging branch is created. This automation can be gated behind approval workflows, ensuring that changes are reviewed before being merged into production.

yaml name: Staging Auto-PR on: create: jobs: pull-request: runs-on: ubuntu-latest if: | github.event.ref_type == 'branch' && github.event.ref == 'staging' permissions: pull-requests: write steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Create Pull Request env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPO: ${{ github.repository }} GITHUB_REF: ${{ github.ref }} run: | gh pr create \ --repo=${GITHUB_REPO} \ --head=staging \ --base=main \ --title="Pulling ${GITHUB_REF} into main" \ --body=":crown: *An automated PR*" \ --reviewer=kingdonb \ --draft

Using Specialized Actions for Package Updates

For routine tasks such as updating dependencies, specialized GitHub Actions can simplify the process. The technote-space/create-pr-action is an example of such a tool. It executes arbitrary commands, commits the changes, and creates a pull request automatically. This action can also manage existing pull requests, resolving conflicts and closing outdated PRs. This is particularly useful for maintaining up-to-date dependencies in projects that use package managers like npm or Composer.

The workflow can be scheduled to run periodically, such as daily, to check for updates. The action executes the necessary commands to update the packages, commits the changes with a specific message, and creates a pull request with a designated title and branch name. This automation reduces the manual effort required to keep dependencies current and ensures that updates are consistently reviewed and merged. The action supports various package managers and can be customized to fit the specific needs of the project.

yaml name: Update packages on: schedule: - cron: 0 0 * * * pull_request: types: [opened, synchronize, reopened, closed] jobs: release: name: Update npm packages runs-on: ubuntu-latest steps: - name: Update npm packages uses: technote-space/create-pr-action@v2 with: EXECUTE_COMMANDS: | npx npm-check-updates -u --packageFile package.json yarn install yarn upgrade yarn audit COMMIT_MESSAGE: 'chore: update npm dependencies' COMMIT_NAME: 'GitHub Actions' COMMIT_EMAIL: '[email protected]' PR_BRANCH_NAME: 'chore-npm-update-${PR_ID}' PR_TITLE: 'chore: update npm dependencies'

Conclusion

Automating the creation of pull requests in GitHub Actions is a powerful technique for streamlining development workflows and maintaining code quality. By moving beyond the limitations of default tokens and leveraging temporary, scoped access tokens, teams can securely automate cross-repository operations. Whether using the GitHub REST API for granular control, the GitHub CLI for streamlined interactions, or specialized actions for routine maintenance, the underlying principle remains the same: precise configuration and robust error handling are essential for reliable automation. The implementation of global and local token policies ensures that these automated actions are secure and restricted to authorized workflows. As organizations continue to adopt more complex CI/CD pipelines, the ability to automate pull requests across multiple repositories will become an increasingly critical component of their development infrastructure.

Sources

  1. Create a pull request into another repository using GitHub Actions
  2. Flux CD: GitHub Actions Auto-PR Use Case
  3. GitHub Marketplace: create-pr-action

Related Posts