Skip to content

Upstream PR Triage Pilot #1392

Upstream PR Triage Pilot

Upstream PR Triage Pilot #1392

Workflow file for this run

# Upstream PR discovery and conservative triage for the Valar Zebra fork.
#
# Scheduled runs batch quiet triage, but open at most one downstream PR.
name: Upstream PR Triage Pilot
on:
workflow_dispatch:
inputs:
mode:
description: "Run mode"
type: choice
options:
- discover
- dry-run
- open-pr
default: discover
source_repo:
description: "Upstream repository to inspect"
type: string
default: ZcashFoundation/zebra
source_ref:
description: "Upstream branch or ref"
type: string
default: main
target_ref:
description: "Fork branch to target"
type: string
default: ironwood-main
candidate_pr:
description: "Optional upstream PR number override for debugging"
type: string
default: ""
limit:
description: "Maximum candidates to triage. Must be between 1 and 25."
type: string
default: "25"
schedule:
- cron: "17 * * * *"
permissions: {}
concurrency:
group: upstream-sync-${{ github.ref }}-${{ inputs.mode || 'open-pr' }}
cancel-in-progress: false
jobs:
discover:
name: Discover missing upstream PR
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
status: ${{ steps.discover.outputs.status }}
has_candidate: ${{ steps.discover.outputs.has_candidate }}
source_pr: ${{ steps.discover.outputs.source_pr }}
branch_name: ${{ steps.discover.outputs.branch_name }}
pr_title: ${{ steps.discover.outputs.pr_title }}
candidate_count: ${{ steps.discover.outputs.candidate_count }}
steps:
- name: Validate inputs
env:
LIMIT: ${{ inputs.limit || '25' }}
run: |
case "$LIMIT" in
""|*[!0-9]*)
echo "upstream-sync limit must be a number between 1 and 25" >&2
exit 1
;;
esac
if [ "$LIMIT" -lt 1 ] || [ "$LIMIT" -gt 25 ]; then
echo "upstream-sync supports limit between 1 and 25" >&2
exit 1
fi
- name: Checkout target branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
ref: ${{ inputs.target_ref || 'ironwood-main' }}
fetch-depth: 0
persist-credentials: false
- name: Discover candidate
id: discover
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SOURCE_REPO: ${{ inputs.source_repo || 'ZcashFoundation/zebra' }}
SOURCE_REF: ${{ inputs.source_ref || 'main' }}
TARGET_REPO: ${{ github.repository }}
TARGET_REF: ${{ inputs.target_ref || 'ironwood-main' }}
CANDIDATE_PR: ${{ inputs.candidate_pr || '' }}
LIMIT: ${{ inputs.limit || '25' }}
run: |
.github/scripts/upstream-sync-discover.py \
--source-repo "$SOURCE_REPO" \
--source-ref "$SOURCE_REF" \
--target-repo "$TARGET_REPO" \
--target-ref "$TARGET_REF" \
--candidate-pr "$CANDIDATE_PR" \
--limit "$LIMIT" \
--output-dir .github/upstream-sync/work \
--write-diffs
- name: Add discovery summary
run: cat .github/upstream-sync/work/summary.md >> "$GITHUB_STEP_SUMMARY"
- name: Upload discovery artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
with:
name: upstream-sync-discovery
path: .github/upstream-sync/work
include-hidden-files: true
codex-patch:
name: Ask Codex to triage candidate
runs-on: ubuntu-latest
needs: discover
if: ${{ (inputs.mode || 'open-pr') != 'discover' && needs.discover.outputs.has_candidate == 'true' }}
permissions:
contents: read
pull-requests: read
outputs:
result_status: ${{ steps.collect.outputs.status }}
has_recordable_decisions: ${{ steps.collect.outputs.has_recordable_decisions }}
steps:
- name: Checkout target branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
ref: ${{ inputs.target_ref || 'ironwood-main' }}
fetch-depth: 0
persist-credentials: false
- name: Download discovery artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
with:
name: upstream-sync-discovery
path: .github/upstream-sync/work
- name: Prepare Codex context
run: .github/scripts/upstream-sync-run.sh prepare
- name: Run Codex
id: codex
uses: openai/codex-action@e0fdf01220eb9a88167c4898839d273e3f2609d1 #v1
with:
openai-api-key: ${{ secrets.CODEX_API_KEY }}
prompt-file: .github/upstream-sync/prompts/adapt-pr.md
output-file: .github/upstream-sync/work/result.json
output-schema-file: .github/upstream-sync/schemas/result.schema.json
sandbox: workspace-write
safety-strategy: drop-sudo
codex-args: '["--ephemeral"]'
allow-users: ${{ github.actor }}
- name: Validate Codex result
run: |
.github/scripts/upstream-sync-validate.sh \
.github/upstream-sync/work/result.json \
.github/upstream-sync/work/candidate.json
- name: Collect Codex patch
id: collect
run: .github/scripts/upstream-sync-run.sh collect-patch
- name: Upload Codex artifact
if: ${{ always() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
with:
name: upstream-sync-codex
path: .github/upstream-sync/work
include-hidden-files: true
record-decision:
name: Record non-import triage decision
runs-on: ubuntu-latest
needs:
- discover
- codex-patch
if: >-
${{
(inputs.mode || 'open-pr') == 'open-pr' &&
needs.discover.outputs.has_candidate == 'true' &&
needs.codex-patch.outputs.has_recordable_decisions == 'true'
}}
permissions:
contents: write
steps:
- name: Checkout target branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
ref: ${{ inputs.target_ref || 'ironwood-main' }}
fetch-depth: 0
persist-credentials: true
- name: Download Codex artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
with:
name: upstream-sync-codex
path: .github/upstream-sync/work
- name: Record triage decision
run: .github/scripts/upstream-sync-run.sh record-decision
open-needs-human-pr:
name: Open human review PR
runs-on: ubuntu-latest
needs:
- discover
- codex-patch
if: ${{ (inputs.mode || 'open-pr') == 'open-pr' && needs.codex-patch.outputs.result_status == 'needs_human' }}
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout target branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
ref: ${{ inputs.target_ref || 'ironwood-main' }}
fetch-depth: 0
persist-credentials: true
- name: Download Codex artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
with:
name: upstream-sync-codex
path: .github/upstream-sync/work
- name: Validate Codex result
run: |
.github/scripts/upstream-sync-validate.sh \
.github/upstream-sync/work/result.json \
.github/upstream-sync/work/candidate.json
git diff --check
- name: Prepare PR metadata
id: pr-meta
run: .github/scripts/upstream-sync-run.sh write-pr-body
- name: Delete stale upstream sync branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: .github/scripts/upstream-sync-run.sh delete-stale-branch
- name: Create human review PR
id: create-pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UPSTREAM_SYNC_TARGET_REF: ${{ inputs.target_ref || 'ironwood-main' }}
run: .github/scripts/upstream-sync-run.sh create-human-review-pr
- name: Verify PR state
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.create-pr.outputs.pull_request_number }}
run: |
gh pr view "$PR_NUMBER" \
--repo "${{ github.repository }}" \
--json isDraft,mergeStateStatus,url \
--jq '.'
open-pr:
name: Open draft PR
runs-on: ubuntu-latest
needs:
- discover
- codex-patch
if: ${{ (inputs.mode || 'open-pr') == 'open-pr' && needs.codex-patch.outputs.result_status == 'applied' }}
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout target branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
ref: ${{ inputs.target_ref || 'ironwood-main' }}
fetch-depth: 0
persist-credentials: true
- name: Download Codex artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
with:
name: upstream-sync-codex
path: .github/upstream-sync/work
- name: Apply generated patch
run: .github/scripts/upstream-sync-run.sh apply-patch
- name: Validate generated changes
run: |
.github/scripts/upstream-sync-validate.sh \
.github/upstream-sync/work/result.json \
.github/upstream-sync/work/candidate.json
git diff --check
git diff --cached --check
- name: Prepare PR metadata
id: pr-meta
run: .github/scripts/upstream-sync-run.sh write-pr-body
- name: Check for changes
id: changes
run: |
if git diff --quiet && git diff --cached --quiet; then
echo "No changes to commit"
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
git diff --stat
git diff --cached --stat
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
- name: Delete stale upstream sync branch
if: steps.changes.outputs.has_changes == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: .github/scripts/upstream-sync-run.sh delete-stale-branch
- name: Create upstream sync PR
id: create-pr
if: steps.changes.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 #v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ steps.pr-meta.outputs.branch }}
base: ${{ inputs.target_ref || 'ironwood-main' }}
title: ${{ steps.pr-meta.outputs.title }}
body-path: .github/upstream-sync/work/pr-body.md
commit-message: ${{ steps.pr-meta.outputs.title }}
sign-commits: true
draft: always-true
delete-branch: false
- name: Verify signed commits
if: >-
${{
steps.create-pr.outputs.pull-request-number != '' &&
steps.create-pr.outputs.pull-request-commits-verified != 'true'
}}
run: |
echo "ERROR: upstream sync PR commits were not verified by GitHub" >&2
exit 1
- name: Verify PR state
if: steps.create-pr.outputs.pull-request-number != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }}
run: |
gh pr view "$PR_NUMBER" \
--repo "${{ github.repository }}" \
--json isDraft,mergeStateStatus,url \
--jq '.'