Skip to content

Commit 2b9f8d1

Browse files
authored
chore: add release workflows and review prompt (#2309)
1 parent d79bd98 commit 2b9f8d1

4 files changed

Lines changed: 316 additions & 0 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Release readiness review
2+
3+
You are Codex running in CI. Produce a release readiness report for this repository.
4+
5+
Steps:
6+
1. Determine the latest release tag (use local tags only):
7+
- `git tag -l 'v*' --sort=-v:refname | head -n1`
8+
2. Set TARGET to the current commit SHA: `git rev-parse HEAD`.
9+
3. Collect diff context for BASE_TAG...TARGET:
10+
- `git diff --stat BASE_TAG...TARGET`
11+
- `git diff --dirstat=files,0 BASE_TAG...TARGET`
12+
- `git diff --name-status BASE_TAG...TARGET`
13+
- `git log --oneline --reverse BASE_TAG..TARGET`
14+
4. Review `.codex/skills/final-release-review/references/review-checklist.md` and analyze the diff.
15+
16+
Output:
17+
- Write the report in the exact format used by `$final-release-review` (see `.codex/skills/final-release-review/SKILL.md`).
18+
- Use the compare URL: `https://github.com/${GITHUB_REPOSITORY}/compare/BASE_TAG...TARGET`.
19+
- Include clear ship/block call and risk levels.
20+
- If no risks are found, include "No material risks identified".
21+
22+
Constraints:
23+
- Output only the report (no code fences, no extra commentary).
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Update release PR on main updates
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
concurrency:
9+
group: release-pr-update
10+
cancel-in-progress: true
11+
12+
permissions:
13+
contents: write
14+
pull-requests: write
15+
16+
jobs:
17+
update-release-pr:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@v6
22+
with:
23+
fetch-depth: 0
24+
- name: Fetch tags
25+
run: git fetch origin --tags --prune
26+
- name: Configure git
27+
run: |
28+
git config user.name "github-actions[bot]"
29+
git config user.email "github-actions[bot]@users.noreply.github.com"
30+
- name: Find release PR
31+
id: find
32+
env:
33+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34+
run: |
35+
set -euo pipefail
36+
base_branch="main"
37+
prs_json="$(gh pr list \
38+
--base "$base_branch" \
39+
--state open \
40+
--search "head:release/v" \
41+
--limit 200 \
42+
--json number,headRefName,isCrossRepository,headRepositoryOwner)"
43+
count="$(echo "$prs_json" | jq '[.[] | select(.isCrossRepository == false) | select(.headRefName|startswith("release/v"))] | length')"
44+
if [ "$count" -eq 0 ]; then
45+
echo "found=false" >> "$GITHUB_OUTPUT"
46+
exit 0
47+
fi
48+
if [ "$count" -gt 1 ]; then
49+
echo "Multiple release PRs found; expected a single release PR." >&2
50+
exit 1
51+
fi
52+
number="$(echo "$prs_json" | jq -r '.[] | select(.isCrossRepository == false) | select(.headRefName|startswith("release/v")) | .number')"
53+
branch="$(echo "$prs_json" | jq -r '.[] | select(.isCrossRepository == false) | select(.headRefName|startswith("release/v")) | .headRefName')"
54+
echo "found=true" >> "$GITHUB_OUTPUT"
55+
echo "number=$number" >> "$GITHUB_OUTPUT"
56+
echo "branch=$branch" >> "$GITHUB_OUTPUT"
57+
- name: Rebase release branch
58+
if: steps.find.outputs.found == 'true'
59+
env:
60+
RELEASE_BRANCH: ${{ steps.find.outputs.branch }}
61+
run: |
62+
set -euo pipefail
63+
git fetch origin main "$RELEASE_BRANCH"
64+
git checkout -B "$RELEASE_BRANCH" "origin/$RELEASE_BRANCH"
65+
git rebase origin/main
66+
- name: Run Codex release review
67+
if: steps.find.outputs.found == 'true'
68+
uses: openai/codex-action@v1
69+
with:
70+
openai-api-key: ${{ secrets.PROD_OPENAI_API_KEY }}
71+
prompt-file: .github/codex/prompts/release-review.md
72+
output-file: release-review.md
73+
safety-strategy: drop-sudo
74+
sandbox: read-only
75+
- name: Update PR body and push
76+
if: steps.find.outputs.found == 'true'
77+
env:
78+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
79+
PR_NUMBER: ${{ steps.find.outputs.number }}
80+
RELEASE_BRANCH: ${{ steps.find.outputs.branch }}
81+
run: |
82+
set -euo pipefail
83+
git push --force-with-lease origin "$RELEASE_BRANCH"
84+
gh pr edit "$PR_NUMBER" --body-file release-review.md

.github/workflows/release-pr.yml

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
name: Create release PR
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: "Version to release (e.g., 0.6.6)"
8+
required: true
9+
10+
permissions:
11+
contents: write
12+
pull-requests: write
13+
14+
jobs:
15+
release-pr:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v6
20+
with:
21+
fetch-depth: 0
22+
ref: main
23+
- name: Setup uv
24+
uses: astral-sh/setup-uv@v5
25+
with:
26+
enable-cache: true
27+
- name: Fetch tags
28+
run: git fetch origin --tags --prune
29+
- name: Ensure release branch does not exist
30+
env:
31+
RELEASE_VERSION: ${{ inputs.version }}
32+
run: |
33+
branch="release/v${RELEASE_VERSION}"
34+
if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then
35+
echo "Branch $branch already exists on origin." >&2
36+
exit 1
37+
fi
38+
- name: Update version
39+
env:
40+
RELEASE_VERSION: ${{ inputs.version }}
41+
run: |
42+
python - <<'PY'
43+
import os
44+
import pathlib
45+
import re
46+
import sys
47+
48+
version = os.environ["RELEASE_VERSION"]
49+
if version.startswith("v"):
50+
print("Version must not start with 'v' (use x.y.z...).", file=sys.stderr)
51+
sys.exit(1)
52+
if ".." in version:
53+
print("Version contains consecutive dots (use x.y.z...).", file=sys.stderr)
54+
sys.exit(1)
55+
if not re.match(r"^\d+\.\d+(\.\d+)*([a-zA-Z0-9\.-]+)?$", version):
56+
print(
57+
"Version must be semver-like (e.g., 0.6.6, 0.6.6-rc1, 0.6.6.dev1).",
58+
file=sys.stderr,
59+
)
60+
sys.exit(1)
61+
path = pathlib.Path("pyproject.toml")
62+
text = path.read_text()
63+
updated, count = re.subn(
64+
r'(?m)^version\s*=\s*"[^\"]+"',
65+
f'version = "{version}"',
66+
text,
67+
)
68+
if count != 1:
69+
print("Expected to update exactly one version line.", file=sys.stderr)
70+
sys.exit(1)
71+
if updated == text:
72+
print("Version already set; no changes made.", file=sys.stderr)
73+
sys.exit(1)
74+
path.write_text(updated)
75+
PY
76+
- name: Sync dependencies
77+
run: make sync
78+
- name: Configure git
79+
run: |
80+
git config user.name "github-actions[bot]"
81+
git config user.email "github-actions[bot]@users.noreply.github.com"
82+
- name: Create release branch and commit
83+
env:
84+
RELEASE_VERSION: ${{ inputs.version }}
85+
run: |
86+
branch="release/v${RELEASE_VERSION}"
87+
git checkout -b "$branch"
88+
git add pyproject.toml uv.lock
89+
if git diff --cached --quiet; then
90+
echo "No changes to commit." >&2
91+
exit 1
92+
fi
93+
git commit -m "Bump version to ${RELEASE_VERSION}"
94+
git push --set-upstream origin "$branch"
95+
- name: Run Codex release review
96+
uses: openai/codex-action@v1
97+
with:
98+
openai-api-key: ${{ secrets.PROD_OPENAI_API_KEY }}
99+
prompt-file: .github/codex/prompts/release-review.md
100+
output-file: release-review.md
101+
safety-strategy: drop-sudo
102+
sandbox: read-only
103+
- name: Build PR body
104+
run: |
105+
python - <<'PY'
106+
import pathlib
107+
108+
report = pathlib.Path("release-review.md").read_text()
109+
pathlib.Path("pr-body.md").write_text(report)
110+
PY
111+
- name: Create or update PR
112+
env:
113+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
114+
RELEASE_VERSION: ${{ inputs.version }}
115+
run: |
116+
head_branch="release/v${RELEASE_VERSION}"
117+
pr_number="$(gh pr list --head "$head_branch" --base "main" --json number --jq '.[0].number // empty')"
118+
if [ -z "$pr_number" ]; then
119+
gh pr create \
120+
--title "Release ${RELEASE_VERSION}" \
121+
--body-file pr-body.md \
122+
--base "main" \
123+
--head "$head_branch"
124+
else
125+
gh pr edit "$pr_number" --title "Release ${RELEASE_VERSION}" --body-file pr-body.md
126+
fi

.github/workflows/release-tag.yml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Tag release on merge
2+
3+
on:
4+
pull_request:
5+
types:
6+
- closed
7+
branches:
8+
- main
9+
10+
permissions:
11+
contents: write
12+
13+
jobs:
14+
tag-release:
15+
if: >-
16+
github.event.pull_request.merged == true &&
17+
startsWith(github.event.pull_request.head.ref, 'release/v')
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Validate merge commit
21+
env:
22+
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
23+
run: |
24+
if [ -z "$MERGE_SHA" ]; then
25+
echo "merge_commit_sha is empty; refusing to tag to avoid tagging the wrong commit." >&2
26+
exit 1
27+
fi
28+
- name: Checkout merge commit
29+
uses: actions/checkout@v6
30+
with:
31+
fetch-depth: 0
32+
ref: ${{ github.event.pull_request.merge_commit_sha }}
33+
- name: Setup Python
34+
uses: actions/setup-python@v5
35+
with:
36+
python-version: "3.11"
37+
- name: Configure git
38+
run: |
39+
git config user.name "github-actions[bot]"
40+
git config user.email "github-actions[bot]@users.noreply.github.com"
41+
- name: Fetch tags
42+
run: git fetch origin --tags --prune
43+
- name: Resolve version
44+
id: version
45+
env:
46+
HEAD_REF: ${{ github.event.pull_request.head.ref }}
47+
run: |
48+
python - <<'PY'
49+
import os
50+
import pathlib
51+
import sys
52+
import tomllib
53+
54+
path = pathlib.Path("pyproject.toml")
55+
data = tomllib.loads(path.read_text())
56+
version = data.get("project", {}).get("version")
57+
if not version:
58+
print("Missing project.version in pyproject.toml.", file=sys.stderr)
59+
sys.exit(1)
60+
61+
head_ref = os.environ.get("HEAD_REF", "")
62+
if head_ref.startswith("release/v"):
63+
expected = head_ref[len("release/v") :]
64+
if expected != version:
65+
print(
66+
f"Version mismatch: branch {expected} vs pyproject {version}.",
67+
file=sys.stderr,
68+
)
69+
sys.exit(1)
70+
71+
output_path = pathlib.Path(os.environ["GITHUB_OUTPUT"])
72+
output_path.write_text(f"version={version}\n")
73+
PY
74+
- name: Create tag
75+
env:
76+
VERSION: ${{ steps.version.outputs.version }}
77+
run: |
78+
if git tag -l "v${VERSION}" | grep -q "v${VERSION}"; then
79+
echo "Tag v${VERSION} already exists; skipping."
80+
exit 0
81+
fi
82+
git tag -a "v${VERSION}" -m "Release v${VERSION}"
83+
git push origin "v${VERSION}"

0 commit comments

Comments
 (0)