create-pull-request icon indicating copy to clipboard operation
create-pull-request copied to clipboard

Private GH repo: Cannot read properties of undefined (reading 'number')

Open ayussh-verma opened this issue 9 months ago • 11 comments
trafficstars

Hello, I'm facing the same issue as #2790 with v7.0.6.

This is a private repo, and I'm pinning to v7.0.6 explicitly. The PR was created successfully, but it isn't getting updated. More specifically, the PR is being force-pushed but the title and body isn't being updated.

Running on ubuntu-24.04. I've tried using v6.0.1 to no avail.

Logs:

Run peter-evans/[email protected]
  with:
    token: ***
    sign-commits: true
    commit-message: chore(release): release TOOL 0.10.0
    title: chore(release): release TOOL 0.10.0
    body: ## TOOL [0.10.0](https://github.com/org/repo/tree/0.10.0)
    branch: release
    committer: user[bot] <blah.user[bot]@users.noreply.github.com>
    author: user[bot] <blah.user[bot]@users.noreply.github.com>
    delete-branch: true
    labels: auto
    signoff: false
    draft: false
    maintainer-can-modify: true
  
Fetching existing pull request
  Attempting update of pull request
  Error: Cannot read properties of undefined (reading 'number')

Code:

      - name: Update PR
        uses: peter-evans/[email protected]
        with:
          token: ${{ steps.get-app-token.outputs.token }}
          sign-commits: true
          commit-message: ${{ needs.determine-title.outputs.title }}
          title: ${{ needs.determine-title.outputs.title }}
          body: ${{ steps.get-changelog-entry.outputs.draft-output }}
          branch: release
          committer: '${{ env.GIT_AUTHOR_USERNAME }} <${{ env.GIT_AUTHOR_EMAIL }}>'
          author: '${{ env.GIT_AUTHOR_USERNAME }} <${{ env.GIT_AUTHOR_EMAIL }}>'
          delete-branch: true
          labels: auto

ayussh-verma avatar Feb 19 '25 19:02 ayussh-verma

Hi @ayussh-verma

Is release an existing branch? If so, that's not how this action works. The branch input is the PR branch that the action creates and manages itself.

If you show me your full workflow I might be able to help you better. It matters how the workflow is triggered, and how you are making changes before the action runs.

peter-evans avatar Feb 21 '25 13:02 peter-evans

Hey @peter-evans, no the release branch is created by this action itself.

Basically, this is a release pipeline. I run the workflow with a version input which bumps my projects version and creates a PR for it (and pushes Docker images, etc). The PR title is dependent on the workflow input. If the release can't go through, I run the workflow again with a new semver. The action then force pushes the branch correctly, but then encounters this error when it tries to update the PR title.

If I approve the changes the workflow merges the PR. so the PR is short-lived.

This is the simplified workflow:


on:
  # Prepare the release
  workflow_dispatch:
    inputs:
      version:
        description: "Release version (Following semver, e.g., 1.2.3)"
        required: true
  # Finalize the release
  pull_request_review:
    types: [submitted]

concurrency:
  group: ${{ github.workflow }}

jobs:
  prepare-release:
	if: ${{ workflow_dispatch ]]
    steps:
      - name: Bump version
        run: modify semver

      - name: Get changelog
        run: build changelog

      - name: Determine PR title
        id: determine-title
        run: build title from semver workflow_dispatch

      - name: Update PR
        id: update-pr
        uses: peter-evans/[email protected]
        with:
          commit-message: ${{ determine-title }}
          title: ${{ determine-title }}
          body: ${{ get-changelog }}
          branch: release
          delete-branch: true
        continue-on-error: true

      # This step is what the action is failing to do.
      - name: Update the PR body and title
        id: update-pr-temp
        run: |
          pr_number=$(fetch release PR)
          gh pr edit "${pr_number}" --body {{ get-changelog }} --title {{ determine-title }}

          ref=$(gh pr view --json headRefOid --jq '.headRefOid')
          echo "pull-request-head-sha=${ref}" >> "${GITHUB_OUTPUT}"

  finalize-release:
    if: ${{
        github.event_name == 'pull_request_review'
        && github.event.review.state == 'approved' 
      }}
    steps:
      - name: Get the semver from the PR title
        id: get-semver
        run: title | awk ${NF}

      - name: Tag the git commit
        run: git tag head_ref

      - name: Create a GitHub release from the git tag

      - name: Merge the PR
        run: gh pr merge --merge --delete-branch
        env:
          GH_TOKEN: ${{ steps.get-app-token.outputs.token }}

ayussh-verma avatar Feb 21 '25 14:02 ayussh-verma

And if you want to see the exact workflow:

name: Release

on:
  # Prepare the release
  workflow_dispatch:
    inputs:
      version:
        description: "Release version (Following semver, e.g., 1.2.3)"
        required: true
  # Finish the release
  pull_request_review:
    types: [submitted]

concurrency:
  group: ${{ github.workflow }}

env:
  GIT_AUTHOR_USERNAME: 'bot-bot[bot]'
  GIT_AUTHOR_EMAIL: '1203103120+bot-bot[bot]@users.noreply.github.com'
  MESSAGE_PREFIX: 'chore(release): release TOOL'
  DOCKER_IMAGE_PATH: 'ghcr.io/${{ github.repository }}'
  PRE_RELEASE_IMAGE_PREFIX: 'pre-release'

jobs:
  clean-dispatch-inputs:
    if: ${{ github.event_name == 'workflow_dispatch' }}
    runs-on: ubuntu-24.04
    timeout-minutes: ${{ fromJSON(vars.SHORT_TIMEOUT) }}

    outputs:
      semver: ${{ steps.clean-version.outputs.version }}

    steps:
      - name: Fail if the workflow hasn't been triggered from main
        if: ${{ github.ref != 'refs/heads/main' }}
        run: echo 'Releases must be made from the main branch' && exit 1

      - name: Fail if the version is not a valid semver
        run: |
          version='${{ github.event.inputs.version }}'
          if ! [[ "${version}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
            echo "Invalid version: ${version}. Must follow semver (e.g., 1.2.3)."
            exit 1
          fi

      - name: Clean up the version
        id: clean-version
        run: |
          version='${{ github.event.inputs.version }}'
          if [[ "${version}" == v* ]]; then
            version="${version:1}"
          fi
          echo "version=${version}" >> "${GITHUB_OUTPUT}"

  prepare-release:
    runs-on: ubuntu-24.04
    timeout-minutes: ${{ fromJSON(vars.MODERATE_TIMEOUT) }}
    needs: clean-dispatch-inputs
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup project tooling
        uses: ./.github/actions/setup-tools
        with:
          install-pre-commit: false

      - name: Bump pyproject.toml version
        shell: uv run --script {0}
        run: |
          import tomlkit

          with open("pyproject.toml") as f:
              pyproject_data = tomlkit.load(f)

          new_version = "${{ needs.clean-dispatch-inputs.outputs.semver }}"
          pyproject_data["project"]["version"] = new_version

          with open("pyproject.toml", "w") as f:
             tomlkit.dump(pyproject_data, f)

      - name: Install new project version
        run: uv sync

      - name: Get towncrier CHANGELOG entry contents
        id: get-changelog-entry
        # PR Title will be chore(release): release SCALE DB Utility vx.y.z
        # PR Body will be the lines added to CHANGELOG.md
        run: |
          draft=$(uv run towncrier build --draft)
          echo "draft-output<<EOF" >> "${GITHUB_OUTPUT}"
          echo "${draft}" >> "${GITHUB_OUTPUT}"
          echo "EOF" >> "${GITHUB_OUTPUT}"

      - name: Get app token
        id: get-app-token
        uses: actions/create-github-app-token@v1
        with:
          private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
          app-id: ${{ secrets.GH_APP_ID }}
          owner: ${{ github.repository_owner }}

      - name: Determine PR title after adding the semver
        id: determine-title
        run: |
          title="${MESSAGE_PREFIX} ${{ needs.clean-dispatch-inputs.outputs.semver }}"
          echo "title=${title}" >> "${GITHUB_OUTPUT}"

      - name: Update PR
        id: update-pr
        uses: peter-evans/[email protected]
        with:
          token: ${{ steps.get-app-token.outputs.token }}
          sign-commits: true
          commit-message: ${{ steps.determine-title.outputs.title }}
          title: ${{ steps.determine-title.outputs.title }}
          body: ${{ steps.get-changelog-entry.outputs.draft-output }}
          branch: release
          committer: '${{ env.GIT_AUTHOR_USERNAME }} <${{ env.GIT_AUTHOR_EMAIL }}>'
          author: '${{ env.GIT_AUTHOR_USERNAME }} <${{ env.GIT_AUTHOR_EMAIL }}>'
          delete-branch: true
          labels: auto
        continue-on-error: true # FIXME: Remove this when the upstream issue gets fixed

      # FIXME: Remove this when the upstream issue gets fixed
      - name: Update the PR body and title
        id: update-pr-temp
        run: |
          # Wait a bit for the REST API to catch up
          sleep 10

          pr_number=$(gh pr list --state open --base main --json number,title | jq -r '.[] | select(.title | startswith("chore(release): release SCALE DB Utility")) | .number')
          gh pr edit "${pr_number}" --body "${{ steps.get-changelog-entry.outputs.draft-output }}" --title "${{ steps.determine-title.outputs.title }}"

          ref=$(gh pr view "${pr_number}" --json headRefOid --jq '.headRefOid')
          echo "pull-request-head-sha=${ref}" >> "${GITHUB_OUTPUT}"
        env:
          GH_TOKEN: ${{ steps.get-app-token.outputs.token }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Get SHAs
        id: get-shas
        run: |
          tag='${{ steps.update-pr-temp.outputs.pull-request-head-sha }}'
          echo "tag=${tag}" >> "${GITHUB_OUTPUT}"

          short_tag=$(cut -c 1-7 <<< "${tag}")
          echo "short-tag=${short_tag}" >> "${GITHUB_OUTPUT}"

      - name: Generate image tags
        id: pre-release-tags
        shell: python {0}
        run: |
          import os

          semver = "${{ needs.clean-dispatch-inputs.outputs.semver }}"
          PRE_RELEASE_PREFIX = os.environ['PRE_RELEASE_IMAGE_PREFIX']

          image_path = "${{ env.DOCKER_IMAGE_PATH }}".lower()
          cache_tag = f"{image_path}:cache"
          pre_release_tag = f"{image_path}:{PRE_RELEASE_PREFIX}"

          tag_with_semver = f"{image_path}:{PRE_RELEASE_PREFIX}-{semver}"
          full_sha = "${{ steps.get-shas.outputs.tag }}"
          short_sha = "${{ steps.get-shas.outputs.short-tag }}"

          absolute_tag = f"{tag_with_semver}-{full_sha}"

          tags = [
              cache_tag,
              pre_release_tag,
              absolute_tag,
              f"{tag_with_semver}-{short_sha}"
          ]

          with open(os.environ['GITHUB_OUTPUT'], "a") as f:
              print(f"cache-tag={cache_tag}", file=f)
              print(f"absolute-tag={absolute_tag}", file=f)

              print("tags<<EOF", file=f)
              for tag in tags:
                  print(tag, file=f)
              print("EOF", file=f)
    
      - name: Checkout PR
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: release

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./Dockerfile
          provenance: mode=max
          sbom: true
          push: true
          cache-from: type=registry,ref=${{ steps.pre-release-tags.outputs.cache-tag }}
          cache-to: type=inline
          tags: ${{ steps.pre-release-tags.outputs.tags }}

      - name: Smoke test the Docker image
        run: |
          docker run --rm ${{ steps.pre-release-tags.outputs.absolute-tag }} --help
          docker run --rm ${{ steps.pre-release-tags.outputs.absolute-tag }} --version

  finalize-release:
    if: ${{
        github.event_name == 'pull_request_review'
        && github.event.review.state == 'approved' 
        && github.event.pull_request.head.ref == 'release' 
        && github.repository == github.event.pull_request.head.repo.full_name
      }}
    runs-on: ubuntu-24.04
    timeout-minutes: ${{ fromJSON(vars.MODERATE_TIMEOUT) }}

    # Tag the git commit with the semver, push the docker image, create a GitHub release from that tag
    # if everything is successful, merge the pr and delete the feature branch
    steps:
      - name: Checkout PR
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: release

      - name: Get app token
        id: get-app-token
        uses: actions/create-github-app-token@v1
        with:
          private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
          app-id: ${{ secrets.GH_APP_ID }}
          owner: ${{ github.repository_owner }}

      - name: Parse PR semver
        id: get-semver
        run: |
          title='${{ github.event.pull_request.title }}'
          # semver is just 'MESSAGE_PREFIX x.y.z'
          semver=$(echo "${title}" | awk '{print $NF}')
          echo "semver=${semver}" >> "${GITHUB_OUTPUT}"

      - name: Tag the git commit
        run: >
          gh api 'repos/${{ github.repository }}/git/refs'
          -f ref='refs/tags/${{ steps.get-semver.outputs.semver }}'
          -f sha='${{ github.event.pull_request.head.sha }}'
        env:
          GH_TOKEN: ${{ steps.get-app-token.outputs.token }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Get new image tags
        id: get-new-tags
        shell: python {0}
        run: |
          import os

          PRE_RELEASE_PREFIX = os.environ['PRE_RELEASE_IMAGE_PREFIX']

          image_path = "${{ env.DOCKER_IMAGE_PATH }}".lower()
          semver = "${{ steps.get-semver.outputs.semver }}"

          head_sha = "${{ github.event.pull_request.head.sha }}"

          pre_release_tag = f"{image_path}:{PRE_RELEASE_PREFIX}-{semver}-{head_sha}"
          main_tag = f"{image_path}:{semver}"
          cache_tag = f"{image_path}:cache"
          main_tag_short_sha = f"{image_path}:{semver}-{head_sha[:7]}"
          latest_tag = f"{image_path}:latest"

          with open(os.environ['GITHUB_OUTPUT'], "a") as f:
              print(f"main-tag={main_tag}", file=f)
              print(f"main-tag-short-sha={main_tag_short_sha}", file=f)
              print(f"latest-tag={latest_tag}", file=f)
              print(f"pre-release-tag={pre_release_tag}", file=f)

      - name: Add the new tags to the Docker image
        run: |
          pre_release_tag='${{ steps.get-new-tags.outputs.pre-release-tag }}'
          main_tag='${{ steps.get-new-tags.outputs.main-tag }}'
          main_tag_short_sha='${{ steps.get-new-tags.outputs.main-tag-short-sha }}'
          latest_tag='${{ steps.get-new-tags.outputs.latest-tag }}'

          docker pull "${pre_release_tag}"
          docker tag "${pre_release_tag}" "${main_tag}"
          docker tag "${pre_release_tag}" "${latest_tag}"
          docker tag "${pre_release_tag}" "${main_tag_short_sha}"

          docker push "${main_tag}"
          docker push "${latest_tag}"
          docker push "${main_tag_short_sha}"

      - name: Create a GitHub release
        uses: softprops/action-gh-release@v2
        with:
          body: ${{ github.event.pull_request.body }}
          tag_name: ${{ steps.get-semver.outputs.semver }}
          token: ${{ steps.get-app-token.outputs.token }}
          target_commitish: ${{ github.event.pull_request.head.sha }}

      - name: Merge the PR
        run: gh pr merge --merge --delete-branch
        env:
          GH_TOKEN: ${{ steps.get-app-token.outputs.token }}

      - name: Remove git tag on failure
        if: ${{ failure() }}
        run: >
          gh api
          'repos/${{ github.repository }}/git/refs/tags/${{ steps.get-semver.outputs.semver }}'
          -X DELETE
        env:
          GH_TOKEN: ${{ steps.get-app-token.outputs.token }}

      - name: Delete the GitHub release
        if: ${{ failure() }}
        run: >
          gh api
          'repos/${{ github.repository }}/releases/tags/${{ steps.get-semver.outputs.semver }}'
          -X DELETE
        env:
          GH_TOKEN: ${{ steps.get-app-token.outputs.token }}

ayussh-verma avatar Feb 21 '25 14:02 ayussh-verma

Oh, I wonder if this is because I'm using a GitHub app. My GitHub app has these permissions:

Organization Members | read Commit statuses | read + write Contents | read + write Issues | read + write Pull requests | read + write Workflows | read + write Administration | read + write Dependabot alerts | read Members | read Metadata | read Projects | read

But if it's a permission issue, then I don't understand why the gh CLI is able to update the PR

ayussh-verma avatar Mar 06 '25 09:03 ayussh-verma

I'm having the same issue, it happens only if I trigger a second run of the same job (for example after a failure).

FezVrasta avatar Apr 08 '25 14:04 FezVrasta

Same issue whilst the PR is already open

iamironz avatar Apr 14 '25 09:04 iamironz

Seeing the same thing (updating a existing PR) (used v7)

fromeijn avatar Apr 28 '25 09:04 fromeijn

Just in case this applies to some users who are experiencing this error. This same error has occurred before, and that particular case was fixed in v5 onwards. If you are using a version less than v5, please update.

peter-evans avatar Apr 28 '25 09:04 peter-evans

At the moment I'm unable to reproduce this. Is it happening consistently? Is anyone able to create a simple example of this so that I can execute it to reproduce the error?

peter-evans avatar Apr 28 '25 09:04 peter-evans

For v7 (peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38) the issue is reproducible whilst updating already opened PR with the same target and source branch. No issues if no PRs opened.

Image

iamironz avatar Apr 30 '25 08:04 iamironz

@iamironz Is this the commit of create-pull-request that you are using? https://github.com/peter-evans/create-pull-request/commit/153407881ec5c347639a548ade7d8ad1d6740e38

If so, that is a very old commit and doesn't contain the fix for the issue when it occurred. Please update.

peter-evans avatar May 03 '25 07:05 peter-evans

I am also seeing this issue in a private forked repo. Interestingly, the PR does get updated, but the job has this error and fails which is unfortunate. It only occurs when the PR is already open. Any suggestions for work arounds?

jacksonneal avatar Sep 09 '25 19:09 jacksonneal

This is now a known issue on forks with the same owner as the parent repo. I left a comment here about it: https://github.com/peter-evans/create-pull-request/pull/4064#issuecomment-3124203252

I'm planning to test the workaround for it, but I just haven't had time yet.

peter-evans avatar Sep 09 '25 19:09 peter-evans

This is now a known issue on forks with the same owner as the parent repo. I left a comment here about it: #4064 (comment)

I'm planning to test the workaround for it, but I just haven't had time yet.

Thanks. I use the action in a couple places without issue so glad to know this only affects forks.

jacksonneal avatar Sep 09 '25 19:09 jacksonneal

thank you

Joe-joey1 avatar Sep 25 '25 04:09 Joe-joey1

steps:
  - uses: actions/checkout@v4
  - name: Create commits
    run: |
      git config user.name 'Peter Evans'
      git config user.email '[email protected]'
      date +%s > report.txt
      git commit -am "Modify tracked file during workflow"
      date +%s > new-report.txt
      git add -A
      git commit -m "Add untracked file during workflow"
  - name: Uncommitted change
    run: date +%s > report.txt
  - name: Create Pull Request
    uses: peter-evans/create-pull-request@v7

Joe-joey1 avatar Sep 25 '25 04:09 Joe-joey1