Skip to content

[CI] Run CI on external fork PRs via approved-for-ci-run label - #458

Open
elenagaljak-db wants to merge 1 commit into
mainfrom
elenagaljak-db-ci
Open

elenagaljak-db wants to merge 1 commit into
mainfrom
elenagaljak-db-ci

Conversation

@elenagaljak-db

@elenagaljak-db elenagaljak-db commented Jun 30, 2026 •

Copy link
Copy Markdown
Collaborator

What changes are proposed in this pull request?

Fork PRs can't run CI: GitHub denies them the OIDC token, so the JFrog auth step fails (ACTIONS_ID_TOKEN_REQUEST_TOKEN unbound).

This adds a pull_request_target workflow: a maintainer (write access)reviews the fork code and adds the approved-for-ci-run label, whichmirrors the PR into an internal ci-run/pr-<N> branch + PR. As a same-repo PR it gets OIDC, so JFrog auth and full CI run normally.
The label is auto-removed so each push needs re-approval.

Requires: approved-for-ci-run label, CI_APP_ID + CI_APP_PRIVATE_KEY secrets, and PR-write on the CI GitHub App.

How is this tested?

@elenagaljak-db elenagaljak-db self-assigned this Jun 30, 2026
@elenagaljak-db
elenagaljak-db marked this pull request as ready for review June 30, 2026 16:09
@elenagaljak-db elenagaljak-db added the enhancement New feature or request label Jun 30, 2026
Signed-off-by: elenagaljak-db <elena.galjak@databricks.com>

- run: gh pr --repo "${GITHUB_REPOSITORY}" edit "${PR_NUMBER}" --remove-label "approved-for-ci-run"

- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This depth-one checkout breaks when a fork is more than one unique commit ahead of main. The base repository does not have the fork-only parent commits, and the shallow checkout cannot supply them when pushing ci-run/pr-N, so the remote rejects the push.

Could we fetch the approved SHA's full ancestry before pushing?

with:
  fetch-depth: 0

# For `git push` and `gh pr create` we use CI_ACCESS_TOKEN


if: |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition accepts any label event while approved-for-ci-run remains present, and it also accepts same-repository PRs that do not need mirroring. If the collaborator lookup fails, bash -e exits before the label is removed, so a later unrelated label can replay the job.

Could we restrict the job to the exact approval label on a fork PR?

if: |
  github.event.action == 'labeled' &&
  github.event.label.name == 'approved-for-ci-run' &&
  github.event.pull_request.head.repo.full_name != github.repository

It would also be good to treat a failed permission lookup as denial and consume approved-for-ci-run in a separate step with if: always(), so every approval attempt removes the label.

steps:
- name: Close CI-run PR and delete the `ci-run/pr-${{ env.PR_NUMBER }}` branch
run: |
CLOSED="$(gh pr --repo "${GITHUB_REPOSITORY}" list --head "${BRANCH}" --json 'closed' --jq '.[].closed')"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleanup only deletes the branch while closing an open mirror PR. If the push succeeds but PR creation fails, or the mirror PR was already closed without deleting its branch, ci-run/pr-N remains in the repository.

Could we close any open mirror and then delete the ref independently?

gh pr list --state open --head "$BRANCH" --json number --jq '.[].number' |
  while read -r number; do
    gh pr close "$number"
  done
gh api --method DELETE "repos/$GITHUB_REPOSITORY/git/refs/heads/$BRANCH" || true

The existing job-scoped GITHUB_TOKEN has contents: write, so this does not need another App token.

labels: linux-ubuntu-latest

steps:
- name: Generate a GitHub App installation token

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The App is already scoped correctly, but neither this token request nor the PR description records the complete boundary. Could we request only Contents: write and Pull requests: write, switch from the deprecated app-id input to client-id, and document that the App is installed only on databricks/zerobus-sdk with no Workflows: write permission?

with:
  client-id: ${{ secrets.CI_APP_CLIENT_ID }}
  private-key: ${{ secrets.CI_APP_PRIVATE_KEY }}
  permission-contents: write
  permission-pull-requests: write

The missing workflow permission means external PRs that change .github/workflows/** cannot use this mirror path. It would be good to document the alternate process for testing those contributions as well.

# Create local PR for an `approved-for-ci-run` labelled PR to run CI pipeline in it.

permissions:
pull-requests: write # for `gh pr edit`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we remove the trailing whitespace here and update the following comment to say GitHub App token instead of CI_ACCESS_TOKEN?

env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
cat << EOF > body.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

body.md is created inside the fork worktree, so a committed symlink at that path can redirect this fixed-content write and fail the run. Could we pass the body directly to gh pr create and include the immutable approved SHA for auditability?

env:
  APPROVED_SHA: ${{ github.event.pull_request.head.sha }}
run: |
  gh pr create \
    --body "CI run for PR #$PR_NUMBER. Approved SHA: $APPROVED_SHA" \
    --head "$BRANCH" \
    --base main \
    --draft

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants