-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Preview issue triage prompt changes #20352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 13 commits
f99fdb8
93e058c
db2df8b
68f329c
aa62c60
c8afac3
58f129e
085f2bd
4e0bb12
60bc74c
996badf
63e99e8
8bae14a
3e89a83
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| # Preview issue triage prompt changes without modifying issues. | ||
| name: "Issue triage preview" | ||
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, reopened] | ||
| paths: | ||
| - ".github/workflows/issue-triage-preview.yml" | ||
| - ".github/workflows/issue-triage.yml" | ||
| - ".github/workflows/reproduce-bug.yml" | ||
| - "agents/codex/**" | ||
| - "agents/prompts/reproduce-bug.md" | ||
| - "agents/prompts/triage-issue.md" | ||
| - "agents/schemas/issue-triage-bug.json" | ||
| - "agents/schemas/issue-triage.json" | ||
| - "agents/scripts/issue-triage-preview.py" | ||
| - "agents/scripts/issue-triage-preview.py.lock" | ||
|
|
||
| permissions: {} | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| collect: | ||
| if: github.repository == 'astral-sh/uv' && github.event.pull_request.head.repo.full_name == github.repository | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| issues: read | ||
| outputs: | ||
| issues: ${{ steps.issues.outputs.issues }} | ||
| steps: | ||
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| persist-credentials: false | ||
| - name: "Install uv" | ||
| uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 | ||
| with: | ||
| version: "0.11.28" | ||
| - name: "Find recent issues" | ||
| id: issues | ||
| run: uv run --locked --no-project --script agents/scripts/issue-triage-preview.py collect | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| triage: | ||
| name: "Triage issue ${{ matrix.issue }}" | ||
| needs: collect | ||
| permissions: | ||
| contents: read | ||
| issues: read | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| issue: ${{ fromJSON(needs.collect.outputs.issues) }} | ||
| uses: ./.github/workflows/issue-triage.yml # zizmor: ignore[secrets-inherit] Required for environment secrets in reusable workflows. | ||
| with: | ||
| issue: ${{ matrix.issue }} | ||
| secrets: inherit | ||
|
Comment on lines
+52
to
+55
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Prevent PR-controlled workflows from receiving automation secrets This
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably add some notes to the threat-model about this. |
||
|
|
||
| report: | ||
| needs: triage | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| steps: | ||
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| persist-credentials: false | ||
| - name: "Install uv" | ||
| uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 | ||
| with: | ||
| version: "0.11.28" | ||
| - name: "Create or update preview comment" | ||
| run: uv run --locked --no-project --script agents/scripts/issue-triage-preview.py comment --pull-request "$PULL_REQUEST_NUMBER" | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| # /// script | ||
| # requires-python = ">=3.12" | ||
| # dependencies = ["httpx"] | ||
| # /// | ||
|
|
||
| """Collect recent issues and update the issue-triage preview comment.""" | ||
|
|
||
| import argparse | ||
| import json | ||
| import os | ||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| import httpx | ||
|
|
||
| MARKER = "<!-- issue-triage-preview -->" | ||
|
|
||
|
|
||
| def request( | ||
| method: str, | ||
| path: str, | ||
| *, | ||
| query: dict[str, str | int] | None = None, | ||
| body: dict[str, str] | None = None, | ||
| ) -> Any: | ||
| response = httpx.request( | ||
| method, | ||
| f"{os.environ['GITHUB_API_URL']}/{path}", | ||
| params=query, | ||
| json=body, | ||
| headers={ | ||
| "Accept": "application/vnd.github+json", | ||
| "Authorization": f"Bearer {os.environ['GH_TOKEN']}", | ||
| "X-GitHub-Api-Version": "2022-11-28", | ||
| }, | ||
| timeout=30, | ||
| ) | ||
| response.raise_for_status() | ||
| return response.json() | ||
|
|
||
|
|
||
| def collect(limit: int) -> None: | ||
| repository = os.environ["GITHUB_REPOSITORY"] | ||
| result = request( | ||
| "GET", | ||
| "search/issues", | ||
| query={ | ||
| "q": f"repo:{repository} is:issue", | ||
| "sort": "created", | ||
| "order": "desc", | ||
| "per_page": limit, | ||
| }, | ||
| ) | ||
| issues = [str(issue["number"]) for issue in result["items"]] | ||
|
|
||
| with Path(os.environ["GITHUB_OUTPUT"]).open("a") as output: | ||
| output.write(f"issues={json.dumps(issues, separators=(',', ':'))}\n") | ||
|
|
||
|
|
||
| def comment(pull_request: int) -> None: | ||
| repository = os.environ["GITHUB_REPOSITORY"] | ||
| comments_path = f"repos/{repository}/issues/{pull_request}/comments" | ||
| comment_id = None | ||
| page = 1 | ||
|
|
||
| while True: | ||
| comments = request("GET", comments_path, query={"per_page": 100, "page": page}) | ||
| for issue_comment in comments: | ||
| if ( | ||
| issue_comment["user"]["login"] == "github-actions[bot]" | ||
| and MARKER in issue_comment["body"] | ||
| ): | ||
| comment_id = issue_comment["id"] | ||
| if len(comments) < 100: | ||
| break | ||
| page += 1 | ||
|
|
||
| run_url = ( | ||
| f"{os.environ['GITHUB_SERVER_URL']}/{repository}/actions/runs/" | ||
| f"{os.environ['GITHUB_RUN_ID']}" | ||
| ) | ||
| body = { | ||
| "body": ( | ||
| f"{MARKER}\n\n" | ||
| "## Issue triage preview\n\n" | ||
| "The issue triage preview is available in the " | ||
| f"[workflow run summary]({run_url})." | ||
| ) | ||
| } | ||
|
|
||
| if comment_id is None: | ||
| request("POST", comments_path, body=body) | ||
| else: | ||
| request("PATCH", f"repos/{repository}/issues/comments/{comment_id}", body=body) | ||
|
|
||
|
|
||
| def main() -> None: | ||
| parser = argparse.ArgumentParser() | ||
| commands = parser.add_subparsers(dest="command", required=True) | ||
|
|
||
| collect_parser = commands.add_parser("collect") | ||
| collect_parser.add_argument("--limit", type=int, default=5) | ||
|
|
||
| comment_parser = commands.add_parser("comment") | ||
| comment_parser.add_argument("--pull-request", type=int, required=True) | ||
|
|
||
| args = parser.parse_args() | ||
| if args.command == "collect": | ||
| collect(args.limit) | ||
| else: | ||
| comment(args.pull_request) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| version = 1 | ||
| revision = 3 | ||
| requires-python = ">=3.12" | ||
|
|
||
| [manifest] | ||
| requirements = [{ name = "httpx" }] | ||
|
|
||
| [[package]] | ||
| name = "anyio" | ||
| version = "4.14.1" | ||
| source = { registry = "https://pypi.org/simple" } | ||
| dependencies = [ | ||
| { name = "idna" }, | ||
| { name = "typing-extensions", marker = "python_full_version < '3.13'" }, | ||
| ] | ||
| sdist = { url = "https://files.pythonhosted.org/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" } | ||
| wheels = [ | ||
| { url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" }, | ||
| ] | ||
|
|
||
| [[package]] | ||
| name = "certifi" | ||
| version = "2026.6.17" | ||
| source = { registry = "https://pypi.org/simple" } | ||
| sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } | ||
| wheels = [ | ||
| { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, | ||
| ] | ||
|
|
||
| [[package]] | ||
| name = "h11" | ||
| version = "0.16.0" | ||
| source = { registry = "https://pypi.org/simple" } | ||
| sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } | ||
| wheels = [ | ||
| { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, | ||
| ] | ||
|
|
||
| [[package]] | ||
| name = "httpcore" | ||
| version = "1.0.9" | ||
| source = { registry = "https://pypi.org/simple" } | ||
| dependencies = [ | ||
| { name = "certifi" }, | ||
| { name = "h11" }, | ||
| ] | ||
| sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } | ||
| wheels = [ | ||
| { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, | ||
| ] | ||
|
|
||
| [[package]] | ||
| name = "httpx" | ||
| version = "0.28.1" | ||
| source = { registry = "https://pypi.org/simple" } | ||
| dependencies = [ | ||
| { name = "anyio" }, | ||
| { name = "certifi" }, | ||
| { name = "httpcore" }, | ||
| { name = "idna" }, | ||
| ] | ||
| sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } | ||
| wheels = [ | ||
| { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, | ||
| ] | ||
|
|
||
| [[package]] | ||
| name = "idna" | ||
| version = "3.18" | ||
| source = { registry = "https://pypi.org/simple" } | ||
| sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } | ||
| wheels = [ | ||
| { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, | ||
| ] | ||
|
|
||
| [[package]] | ||
| name = "typing-extensions" | ||
| version = "4.16.0" | ||
| source = { registry = "https://pypi.org/simple" } | ||
| sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } | ||
| wheels = [ | ||
| { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Do not run PR-controlled workflows with environment secrets
This local reusable workflow is resolved from the pull request revision, and its
triagejob enters the unprotectedautomationsenvironment and receivesOPENAI_API_KEY. Consequently, anyone able to push a same-repository branch can modify the called workflow or its Codex configuration and expose or misuse that credential before review. The same-repository check only excludes forks; it does not establish that the executed revision is trusted. Keep this preview secretless, or move the credentialed stage to protected default-branch workflow code behind an exact-SHA-bound approval and environment protection.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is legitimate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same repo branch == a maintainer's branch, so, likely not?