fix: harden example auto-runs against PATH and port conflicts #797
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| name: Auto label PRs | |
| on: | |
| pull_request_target: | |
| types: | |
| - opened | |
| - reopened | |
| - synchronize | |
| - ready_for_review | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: "PR number to label." | |
| required: true | |
| type: number | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| jobs: | |
| label: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Ensure main workflow | |
| if: ${{ github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main' }} | |
| run: | | |
| echo "This workflow must be dispatched from main." | |
| exit 1 | |
| - name: Resolve PR context | |
| id: pr | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd | |
| env: | |
| MANUAL_PR_NUMBER: ${{ inputs.pr_number || '' }} | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const isManual = context.eventName === 'workflow_dispatch'; | |
| let pr; | |
| if (isManual) { | |
| const prNumber = Number(process.env.MANUAL_PR_NUMBER); | |
| if (!prNumber) { | |
| core.setFailed('workflow_dispatch requires pr_number input.'); | |
| return; | |
| } | |
| const { data } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: prNumber, | |
| }); | |
| pr = data; | |
| } else { | |
| pr = context.payload.pull_request; | |
| } | |
| if (!pr) { | |
| core.setFailed('Missing pull request context.'); | |
| return; | |
| } | |
| const headRepo = pr.head.repo.full_name; | |
| const repoFullName = `${context.repo.owner}/${context.repo.repo}`; | |
| core.setOutput('pr_number', pr.number); | |
| core.setOutput('base_sha', pr.base.sha); | |
| core.setOutput('head_sha', pr.head.sha); | |
| core.setOutput('head_repo', headRepo); | |
| core.setOutput('is_fork', headRepo !== repoFullName); | |
| core.setOutput('title', pr.title || ''); | |
| core.setOutput('body', pr.body || ''); | |
| - name: Checkout base | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ steps.pr.outputs.base_sha }} | |
| - name: Fetch PR head | |
| env: | |
| PR_HEAD_REPO: ${{ steps.pr.outputs.head_repo }} | |
| PR_HEAD_SHA: ${{ steps.pr.outputs.head_sha }} | |
| run: | | |
| set -euo pipefail | |
| git fetch --no-tags --prune --recurse-submodules=no \ | |
| "https://github.com/${PR_HEAD_REPO}.git" \ | |
| "${PR_HEAD_SHA}" | |
| - name: Collect PR diff | |
| id: diff | |
| env: | |
| PR_BASE_SHA: ${{ steps.pr.outputs.base_sha }} | |
| PR_HEAD_SHA: ${{ steps.pr.outputs.head_sha }} | |
| PR_TITLE: ${{ steps.pr.outputs.title }} | |
| PR_BODY: ${{ steps.pr.outputs.body }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p .tmp/pr-labels | |
| diff_base_sha="$(git merge-base "$PR_BASE_SHA" "$PR_HEAD_SHA")" | |
| echo "diff_base_sha=${diff_base_sha}" >> "$GITHUB_OUTPUT" | |
| git diff --name-only "$diff_base_sha" "$PR_HEAD_SHA" > .tmp/pr-labels/changed-files.txt | |
| git diff "$diff_base_sha" "$PR_HEAD_SHA" > .tmp/pr-labels/changes.diff | |
| python - <<'PY' | |
| import json | |
| import os | |
| import pathlib | |
| pathlib.Path(".tmp/pr-labels/pr-context.json").write_text( | |
| json.dumps( | |
| { | |
| "title": os.environ.get("PR_TITLE", ""), | |
| "body": os.environ.get("PR_BODY", ""), | |
| }, | |
| ensure_ascii=False, | |
| indent=2, | |
| ) | |
| + "\n" | |
| ) | |
| PY | |
| - name: Prepare Codex output | |
| id: codex-output | |
| run: | | |
| set -euo pipefail | |
| output_dir=".tmp/codex/outputs" | |
| output_file="${output_dir}/pr-labels.json" | |
| mkdir -p "$output_dir" | |
| echo "output_file=${output_file}" >> "$GITHUB_OUTPUT" | |
| - name: Run Codex labeling | |
| id: run_codex | |
| if: ${{ (github.event_name == 'workflow_dispatch' || steps.pr.outputs.is_fork != 'true') && github.actor != 'dependabot[bot]' }} | |
| uses: openai/codex-action@086169432f1d2ab2f4057540b1754d550f6a1189 | |
| with: | |
| openai-api-key: ${{ secrets.PROD_OPENAI_API_KEY }} | |
| prompt-file: .github/codex/prompts/pr-labels.md | |
| output-file: ${{ steps.codex-output.outputs.output_file }} | |
| output-schema-file: .github/codex/schemas/pr-labels.json | |
| # Keep the legacy Linux sandbox path until the default bubblewrap path | |
| # works reliably on GitHub-hosted Ubuntu runners. | |
| codex-args: '["--enable","use_legacy_landlock"]' | |
| safety-strategy: drop-sudo | |
| sandbox: read-only | |
| - name: Apply labels | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ steps.pr.outputs.pr_number }} | |
| PR_BASE_SHA: ${{ steps.diff.outputs.diff_base_sha }} | |
| PR_HEAD_SHA: ${{ steps.pr.outputs.head_sha }} | |
| CODEX_OUTPUT_PATH: ${{ steps.codex-output.outputs.output_file }} | |
| CODEX_CONCLUSION: ${{ steps.run_codex.conclusion }} | |
| run: | | |
| python .github/scripts/pr_labels.py | |
| - name: Comment on manual run failure | |
| if: ${{ github.event_name == 'workflow_dispatch' && always() }} | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd | |
| env: | |
| PR_NUMBER: ${{ steps.pr.outputs.pr_number }} | |
| JOB_STATUS: ${{ job.status }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| CODEX_CONCLUSION: ${{ steps.run_codex.conclusion }} | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const marker = '<!-- pr-labels-manual-run -->'; | |
| const jobStatus = process.env.JOB_STATUS; | |
| if (jobStatus === 'success') { | |
| return; | |
| } | |
| const prNumber = Number(process.env.PR_NUMBER); | |
| if (!prNumber) { | |
| core.setFailed('Missing PR number for manual run comment.'); | |
| return; | |
| } | |
| const body = [ | |
| marker, | |
| 'Manual PR labeling failed.', | |
| `Job status: ${jobStatus}.`, | |
| `Run: ${process.env.RUN_URL}.`, | |
| `Codex labeling: ${process.env.CODEX_CONCLUSION}.`, | |
| ].join('\n'); | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| per_page: 100, | |
| }); | |
| const existing = comments.find( | |
| (comment) => | |
| comment.user?.login === 'github-actions[bot]' && | |
| comment.body?.includes(marker), | |
| ); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| core.info(`Updated existing comment ${existing.id}`); | |
| return; | |
| } | |
| const { data: created } = await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body, | |
| }); | |
| core.info(`Created comment ${created.id}`); |