Skip to content

Auto Merge Codex Remediation PR #420

Auto Merge Codex Remediation PR

Auto Merge Codex Remediation PR #420

name: Auto Merge Codex Remediation PR
"on":
workflow_run:
workflows: ["CI"]
types: [completed]
jobs:
auto-merge:
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_repository.full_name == github.repository && startsWith(github.event.workflow_run.head_branch, 'codex/monthly-review-issue-') && contains(fromJSON('["true","True","TRUE"]'), vars.CODEX_AUDIT_AUTO_MERGE)
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
issues: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ github.event.repository.default_branch }}
- name: Resolve Codex remediation PR
id: pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
mkdir -p data/output/codex_auto_merge
branch="${{ github.event.workflow_run.head_branch }}"
pr_payload=$(gh pr list --repo "${{ github.repository }}" --state open --head "${branch}" --json number,headRefOid,headRepository,isCrossRepository \
--jq '[.[] | select(.isCrossRepository == false and .headRepository.nameWithOwner == "${{ github.repository }}")][0] // {}')
pr_number=$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("number", ""))' <<<"${pr_payload}")
if [ -z "${pr_number}" ]; then
echo "No same-repository open PR found for ${branch}." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
pr_head_sha=$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("headRefOid", ""))' <<<"${pr_payload}")
if [ "${pr_head_sha}" != "${{ github.event.workflow_run.head_sha }}" ]; then
echo "Skipping auto-merge for stale CI success on ${branch}; workflow_run head SHA does not match the current PR head." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
echo "pr_number=${pr_number}" >> "$GITHUB_OUTPUT"
- name: Evaluate merge guard
id: merge_guard
if: steps.pr.outputs.pr_number != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
gh pr view "${{ steps.pr.outputs.pr_number }}" --repo "${{ github.repository }}" \
--json number,isDraft,body,url,files,changedFiles,additions,deletions,reviewDecision,labels,baseRefName,headRefName,headRepositoryOwner,headRepository,isCrossRepository > data/output/codex_auto_merge/pr.json
gh api --paginate --slurp \
"/repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.pr_number }}/files?per_page=100" \
> data/output/codex_auto_merge/pr_files_pages.json
python3 - <<'PY'
import json
from pathlib import Path
output_dir = Path("data/output/codex_auto_merge")
pr_path = output_dir / "pr.json"
pr = json.loads(pr_path.read_text(encoding="utf-8"))
pages = json.loads((output_dir / "pr_files_pages.json").read_text(encoding="utf-8"))
files = []
for page in pages:
if not isinstance(page, list):
continue
for item in page:
if not isinstance(item, dict):
continue
files.append(
{
"path": item.get("filename", ""),
"status": item.get("status", ""),
"additions": item.get("additions"),
"deletions": item.get("deletions"),
"previous_filename": item.get("previous_filename", ""),
}
)
pr["files"] = files
pr_path.write_text(json.dumps(pr, indent=2, sort_keys=True) + "\n", encoding="utf-8")
PY
python3 scripts/evaluate_codex_pr_merge.py \
--pr-json data/output/codex_auto_merge/pr.json \
--summary-file data/output/codex_auto_merge/summary.md \
--decision-file data/output/codex_auto_merge/decision.json \
--expected-base-ref "${{ github.event.repository.default_branch }}" \
--expected-head-ref "${{ github.event.workflow_run.head_branch }}" \
--expected-head-owner "${{ github.repository_owner }}" \
--expected-head-repository "${{ github.repository }}" \
--require-same-repository
python3 - <<'PY' >> "$GITHUB_OUTPUT"
import json
from pathlib import Path
decision = json.loads(Path("data/output/codex_auto_merge/decision.json").read_text(encoding="utf-8"))
print(f"should_merge={str(bool(decision['should_merge'])).lower()}")
print(f"reason={decision['reason']}")
print(f"risk_level={decision['risk_level']}")
PY
- name: Append merge guard summary
if: steps.pr.outputs.pr_number != ''
run: cat data/output/codex_auto_merge/summary.md >> "$GITHUB_STEP_SUMMARY"
- name: Comment auto-merge guard decision
if: steps.pr.outputs.pr_number != '' && steps.merge_guard.outputs.should_merge != 'true'
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
python3 scripts/post_codex_auto_merge_decision_comment.py \
--repo "${{ github.repository }}" \
--pr-json data/output/codex_auto_merge/pr.json \
--decision-json data/output/codex_auto_merge/decision.json \
--output-file data/output/codex_auto_merge/guard_decision_comment.md \
--sync-labels
- name: Check guarded auto-merge readiness before merge
id: merge_readiness
if: steps.merge_guard.outputs.should_merge == 'true'
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.CODEX_AUDIT_READINESS_TOKEN || secrets.GITHUB_TOKEN }}
CODEX_AUDIT_REQUIRED_STATUS_CHECKS: ${{ vars.CODEX_AUDIT_REQUIRED_STATUS_CHECKS || 'test' }}
run: |
set -euo pipefail
python3 scripts/check_codex_auto_merge_readiness.py \
--repo "${{ github.repository }}" \
--branch "${{ github.event.repository.default_branch }}" \
--auto-merge true \
--required-status-checks "${CODEX_AUDIT_REQUIRED_STATUS_CHECKS}" \
--summary-file data/output/codex_auto_merge/readiness.md
- name: Comment merge-time readiness failure
if: steps.merge_guard.outputs.should_merge == 'true' && steps.merge_readiness.outcome != 'success'
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
python3 - <<'PY'
import json
from pathlib import Path
decision_path = Path("data/output/codex_auto_merge/decision.json")
decision = json.loads(decision_path.read_text(encoding="utf-8"))
risk_reasons = list(decision.get("risk_reasons") or [])
risk_reasons.append("merge-time readiness check failed; see codex_auto_merge/readiness.md")
metadata_errors = list(decision.get("metadata_errors") or [])
metadata_errors.append("merge-time readiness check failed")
decision.update(
{
"should_merge": False,
"reason": "merge_readiness_failed",
"risk_level": "high",
"risk_reasons": risk_reasons,
"metadata_errors": metadata_errors,
}
)
Path("data/output/codex_auto_merge/readiness_decision.json").write_text(
json.dumps(decision, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
PY
python3 scripts/post_codex_auto_merge_decision_comment.py \
--repo "${{ github.repository }}" \
--pr-json data/output/codex_auto_merge/pr.json \
--decision-json data/output/codex_auto_merge/readiness_decision.json \
--output-file data/output/codex_auto_merge/readiness_guard_decision_comment.md \
--sync-labels
- name: Fail on merge-time readiness failure
if: steps.merge_guard.outputs.should_merge == 'true' && steps.merge_readiness.outcome != 'success'
run: |
echo "Guarded auto-merge readiness failed at merge time; see readiness.md and readiness_guard_decision_comment.md." >&2
exit 1
- name: Merge Codex remediation PR
if: steps.merge_guard.outputs.should_merge == 'true' && steps.merge_readiness.outcome == 'success'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr merge "${{ steps.pr.outputs.pr_number }}" --repo "${{ github.repository }}" --rebase --delete-branch --match-head-commit "${{ github.event.workflow_run.head_sha }}"
- name: Upload Codex auto-merge diagnostics
if: always()
uses: actions/upload-artifact@v7
with:
name: codex-auto-merge-${{ github.run_id }}
path: data/output/codex_auto_merge/
if-no-files-found: warn