Skip to content

Commit 7b6a071

Browse files
committed
ci: add Codex-based PR auto-labeling workflow
1 parent 6ab83d4 commit 7b6a071

2 files changed

Lines changed: 191 additions & 0 deletions

File tree

.github/codex/prompts/pr-labels.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# PR auto-labeling
2+
3+
You are Codex running in CI to propose labels for a pull request in the openai-agents-python repository.
4+
5+
Inputs:
6+
- PR diff: .tmp/pr-labels/changes.diff
7+
- Changed files: .tmp/pr-labels/changed-files.txt
8+
9+
Task:
10+
- Inspect the diff and changed files.
11+
- Output JSON with a single top-level key: "labels" (array of strings).
12+
- Include every label that applies. Only use labels from the allowed list.
13+
14+
Allowed labels:
15+
- documentation
16+
- project
17+
- bug
18+
- enhancement
19+
- dependencies
20+
- feature:chat-completions
21+
- feature:core
22+
- feature:lite-llm
23+
- feature:mcp
24+
- feature:realtime
25+
- feature:sessions
26+
- feature:tracing
27+
- feature:voice
28+
29+
Label rules:
30+
- documentation: Documentation changes (docs/), or src/ changes that only modify comments/docstrings without behavior changes. If only comments/docstrings change in src/, do not add bug/enhancement.
31+
- project: Any change to pyproject.toml.
32+
- dependencies: Dependencies are added/removed/updated (pyproject.toml dependency sections or uv.lock changes).
33+
- bug: src/ code changes that fix incorrect behavior.
34+
- enhancement: src/ code changes that add or expand functionality.
35+
- feature:chat-completions: Changes related to Chat Completions integration or conversion, including the litellm chat completions converter.
36+
- feature:core: Core agent loop, tool calls, run pipeline, or other central runtime behavior. Do not use this if the changes are only in other feature areas (extensions, mcp, etc.). If both core and other areas changed, include both.
37+
- feature:lite-llm: Litellm adapter/provider changes.
38+
- feature:mcp: MCP integration changes.
39+
- feature:realtime: Realtime agent changes.
40+
- feature:sessions: Sessions/memory handling changes.
41+
- feature:tracing: Tracing feature changes.
42+
- feature:voice: Voice pipeline (stt -> llm -> tts) or related components.
43+
44+
Output:
45+
- JSON only (no code fences, no extra text).
46+
- Example: {"labels":["enhancement","feature:core"]}

.github/workflows/pr-labels.yml

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
name: Auto label PRs
2+
3+
on:
4+
pull_request_target:
5+
types:
6+
- opened
7+
- reopened
8+
- synchronize
9+
- ready_for_review
10+
11+
permissions:
12+
contents: read
13+
pull-requests: write
14+
15+
jobs:
16+
label:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Checkout base
20+
uses: actions/checkout@v6
21+
with:
22+
fetch-depth: 0
23+
ref: ${{ github.event.pull_request.base.sha }}
24+
- name: Fetch PR head
25+
env:
26+
PR_NUMBER: ${{ github.event.pull_request.number }}
27+
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
28+
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
29+
run: |
30+
set -euo pipefail
31+
git fetch origin "refs/pull/${PR_NUMBER}/head:pr-head"
32+
git cat-file -e "${PR_BASE_SHA}^{commit}"
33+
git cat-file -e "${PR_HEAD_SHA}^{commit}" || git cat-file -e "pr-head^{commit}"
34+
- name: Collect PR diff
35+
env:
36+
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
37+
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
38+
run: |
39+
set -euo pipefail
40+
mkdir -p .tmp/pr-labels
41+
git diff --name-only "$PR_BASE_SHA" "$PR_HEAD_SHA" > .tmp/pr-labels/changed-files.txt
42+
git diff "$PR_BASE_SHA" "$PR_HEAD_SHA" > .tmp/pr-labels/changes.diff
43+
- name: Prepare Codex output
44+
id: codex-output
45+
run: |
46+
set -euo pipefail
47+
output_dir=".tmp/codex/outputs"
48+
output_file="${output_dir}/pr-labels.json"
49+
mkdir -p "$output_dir"
50+
echo "output_file=${output_file}" >> "$GITHUB_OUTPUT"
51+
- name: Run Codex labeling
52+
uses: openai/codex-action@v1
53+
with:
54+
openai-api-key: ${{ secrets.PROD_OPENAI_API_KEY }}
55+
prompt-file: .github/codex/prompts/pr-labels.md
56+
output-file: ${{ steps.codex-output.outputs.output_file }}
57+
safety-strategy: drop-sudo
58+
sandbox: read-only
59+
- name: Apply labels
60+
env:
61+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62+
PR_NUMBER: ${{ github.event.pull_request.number }}
63+
CODEX_OUTPUT_PATH: ${{ steps.codex-output.outputs.output_file }}
64+
run: |
65+
python - <<'PY'
66+
import json
67+
import os
68+
import pathlib
69+
import subprocess
70+
71+
pr_number = os.environ["PR_NUMBER"]
72+
codex_output_path = pathlib.Path(os.environ["CODEX_OUTPUT_PATH"])
73+
changed_files_path = pathlib.Path(".tmp/pr-labels/changed-files.txt")
74+
75+
changed_files = []
76+
if changed_files_path.exists():
77+
changed_files = [
78+
line.strip()
79+
for line in changed_files_path.read_text().splitlines()
80+
if line.strip()
81+
]
82+
83+
desired = set()
84+
if "pyproject.toml" in changed_files:
85+
desired.add("project")
86+
if any(path.startswith("docs/") for path in changed_files):
87+
desired.add("documentation")
88+
if "uv.lock" in changed_files:
89+
desired.add("dependencies")
90+
91+
allowed = {
92+
"documentation",
93+
"project",
94+
"bug",
95+
"enhancement",
96+
"dependencies",
97+
"feature:chat-completions",
98+
"feature:core",
99+
"feature:lite-llm",
100+
"feature:mcp",
101+
"feature:realtime",
102+
"feature:sessions",
103+
"feature:tracing",
104+
"feature:voice",
105+
}
106+
107+
codex_labels = []
108+
if codex_output_path.exists():
109+
raw = codex_output_path.read_text().strip()
110+
if raw:
111+
try:
112+
payload = json.loads(raw)
113+
if isinstance(payload, dict):
114+
labels = payload.get("labels", [])
115+
if isinstance(labels, list):
116+
codex_labels = [label for label in labels if isinstance(label, str)]
117+
except json.JSONDecodeError:
118+
pass
119+
120+
for label in codex_labels:
121+
if label in allowed:
122+
desired.add(label)
123+
124+
result = subprocess.check_output(
125+
["gh", "pr", "view", pr_number, "--json", "labels", "--jq", ".labels[].name"],
126+
text=True,
127+
).strip()
128+
existing = {label for label in result.splitlines() if label}
129+
130+
managed = set(allowed)
131+
to_add = sorted(desired - existing)
132+
to_remove = sorted((existing & managed) - desired)
133+
134+
if not to_add and not to_remove:
135+
print("Labels already up to date.")
136+
raise SystemExit(0)
137+
138+
cmd = ["gh", "pr", "edit", pr_number]
139+
if to_add:
140+
cmd += ["--add-label", ",".join(to_add)]
141+
if to_remove:
142+
cmd += ["--remove-label", ",".join(to_remove)]
143+
144+
subprocess.check_call(cmd)
145+
PY

0 commit comments

Comments
 (0)