From 0862ab0994a78234977aa92c74fde8010f58f205 Mon Sep 17 00:00:00 2001 From: Kelly Guo Date: Wed, 26 Aug 2026 10:56:46 -0700 Subject: [PATCH 1/2] Require pre-commit for inferred backports (#7355) # Description Require inference-resolved release backports to pass the repository pre-commit suite before the workflow can commit the resolution or open a draft PR. The resolver prompt now calls out the pre-commit requirement. If hooks make automatic fixes, the workflow stages only paths from the original source PR, revalidates the candidate scope, and runs pre-commit again. Any remaining failure stops the backport without creating a PR. ## Type of change - Bug fix (non-breaking change which fixes an issue) ## Release backport - [ ] Backport this pull request to the active release branch after it merges into `develop` Not applicable for this default-branch PR; a matching `develop` PR carries the change to the active release branch. ## Validation - `uv run isaaclab -f` - Parsed the workflow YAML - Checked the embedded pre-commit shell block with `bash -n` ## Checklist - [x] I have read and understood the contribution guidelines - [x] I have run the pre-commit checks - [x] My changes generate no new warnings - [x] No documentation, unit-test, or package changelog change is required for this workflow-only fix - [x] My name already exists in `CONTRIBUTORS.md` --- .github/scripts/resolve_backport_conflicts.py | 2 +- .github/workflows/backport-release-3.0.yml | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/.github/scripts/resolve_backport_conflicts.py b/.github/scripts/resolve_backport_conflicts.py index c9a89358fa1..f5b916e3f86 100644 --- a/.github/scripts/resolve_backport_conflicts.py +++ b/.github/scripts/resolve_backport_conflicts.py @@ -203,7 +203,7 @@ def _completion_payload(model: str, context: dict[str, Any]) -> dict[str, Any]: Return a complete final UTF-8 file body for every required write action. Return empty content for every required delete action. Use exactly the paths and required actions supplied. Do not add cleanup or changes unrelated to the source -commit. +commit. The final files must pass the repository's pre-commit hooks. Return only one JSON object without Markdown. It must satisfy this JSON Schema exactly: {json.dumps(schema, ensure_ascii=False, separators=(",", ":"))}""" diff --git a/.github/workflows/backport-release-3.0.yml b/.github/workflows/backport-release-3.0.yml index 60acc8164cf..b2063628c70 100644 --- a/.github/workflows/backport-release-3.0.yml +++ b/.github/workflows/backport-release-3.0.yml @@ -245,6 +245,40 @@ jobs: --model "$primary_model" \ --model "$fallback_model" + - name: Set up Python for inferred-resolution checks + if: steps.cherry_pick.outputs.conflict == 'true' + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.12" + + - name: Run pre-commit on inferred resolution + if: steps.cherry_pick.outputs.conflict == 'true' + working-directory: repository + env: + SKIP: check-changelog-fragments + SOURCE_PARENT: ${{ steps.source.outputs.source_parent }} + SOURCE_SHA: ${{ github.event.pull_request.merge_commit_sha }} + TARGET_SHA: ${{ steps.source.outputs.target_sha }} + run: | + set -euo pipefail + python -m pip install pre-commit==4.6.2 + + set +e + pre-commit run --show-diff-on-failure --color=always --all-files + pre_commit_status=$? + set -e + + if [ "$pre_commit_status" -ne 0 ]; then + while IFS= read -r -d '' path; do + git add --all -- "$path" + done < <(git diff --no-renames --name-only -z "$SOURCE_PARENT" "$SOURCE_SHA" --) + python3 ../automation/.github/scripts/backport.py validate-candidate \ + --source_parent "$SOURCE_PARENT" \ + --source "$SOURCE_SHA" \ + --target "$TARGET_SHA" + pre-commit run --show-diff-on-failure --color=always --all-files + fi + - name: Validate and commit inferred resolution if: steps.cherry_pick.outputs.conflict == 'true' working-directory: repository From 367e498138c233e56896f3deb818aaed8a094dd8 Mon Sep 17 00:00:00 2001 From: Kelly Guo Date: Wed, 26 Aug 2026 10:58:19 -0700 Subject: [PATCH 2/2] Run Docker CI from PR comments (#7059) ## Summary - allow PR authors and repository users with write access to request Docker CI with `run-ci` - use the existing `isaaclab-bot` App token to trigger the normal pull request workflow - stop the base and cuRobo Docker builds from running on ordinary PR opens and pushes - preserve changed-path detection, downstream test dependencies, and maintainer dispatches ## Why The two self-hosted GPU Docker builds currently start automatically whenever relevant files change. PR authors need a self-service way to defer that expensive CI path without requiring a protected environment, a webhook service, or write access to launch `workflow_dispatch`. ## Impact Commenting `run-ci` on an open pull request starts Docker CI when the commenter is the PR author or has write access to the repository. The trusted command workflow never checks out PR code; it briefly applies `ci:run-docker` with `isaaclab-bot`, which triggers `Docker + Tests` as a normal `pull_request` workflow on the current merge ref. The label is removed immediately so the command can be used again after later pushes. GitHub cannot filter `pull_request` label names at workflow-trigger time. Any label applied manually therefore starts the same path-gated Docker workflow; the repository's automatic labeler uses `GITHUB_TOKEN`, whose label events do not create recursive workflow runs. Keeping the workflow unfiltered ensures that a manually labeled run cannot satisfy required checks by skipping them. Maintainers can also use the existing manual workflow dispatch. The upstream `ci:run-docker` label has been created with the description "Trigger the on-demand Docker and GPU CI workflow." ## Validation - `uv run isaaclab -f` before commit - `uv run isaaclab -f` after commit and before push - verified PR-author bypass plus write/admin and read-only permission handling - verified the release ruleset's required Docker/test contexts remain produced only by a real labeled or dispatched run --------- Signed-off-by: Matthew Taylor Co-authored-by: Matthew Taylor Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .github/PULL_REQUEST_TEMPLATE.md | 3 + .github/workflows/build.yaml | 13 +++- .github/workflows/run-docker-ci.yml | 107 ++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/run-docker-ci.yml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index bac78cc0a10..4cb950a3d6d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -51,6 +51,9 @@ To upload images to a PR -- simply drag and drop an image while in edit mode and ## Checklist +Docker and GPU tests run on demand. Push the commits you want tested, then +comment `run-ci` on the pull request. + - [ ] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [ ] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./isaaclab.sh --format` - [ ] I have made corresponding changes to the documentation diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3d7c061fe15..1290eed4a7b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,7 +4,14 @@ # SPDX-License-Identifier: BSD-3-Clause # region help -# Every test job runs on every PR. +# Docker builds and their dependent tests run only on a labeled PR event or a +# manual workflow dispatch. Do not filter the label with a job-level ``if``: +# GitHub treats skipped required jobs as successful status checks. +# +# GitHub cannot filter label names at trigger time, so adding any label to a PR +# starts this workflow. Only a ``ci:run-docker`` event cancels an in-flight run; +# other label events queue behind it so that a triage label cannot cancel a +# build whose required checks have already been reported. # # ============================================================================= # CI DEBUGGING TIPS @@ -47,7 +54,7 @@ name: Docker + Tests on: pull_request: - types: [opened, synchronize, reopened] + types: [labeled] branches: - main - develop @@ -57,7 +64,7 @@ on: # Concurrency control to prevent parallel runs on the same PR concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.event.label.name == 'ci:run-docker' }} permissions: contents: read diff --git a/.github/workflows/run-docker-ci.yml b/.github/workflows/run-docker-ci.yml new file mode 100644 index 00000000000..b9ef223324c --- /dev/null +++ b/.github/workflows/run-docker-ci.yml @@ -0,0 +1,107 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +name: Run Docker CI Command +run-name: Run Docker CI for PR #${{ github.event.issue.number }} + +on: + # GitHub always runs ``issue_comment`` workflows from the repository default + # branch, so this file has to land there for the ``run-ci`` command to work. + issue_comment: + types: [created] + +permissions: + contents: read + pull-requests: read + +concurrency: + group: run-docker-ci-${{ github.event.issue.number }} + cancel-in-progress: false + +jobs: + request-docker-ci: + name: Request Docker CI + if: >- + github.event.issue.pull_request && + github.event.comment.body == 'run-ci' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Authorize the request + id: authorize + env: + COMMENT_AUTHOR: ${{ github.event.comment.user.login }} + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.issue.number }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + + read -r pr_author pr_state head_sha < <( + gh api "repos/$REPOSITORY/pulls/$PR_NUMBER" --jq '[.user.login, .state, .head.sha] | @tsv' + ) + if [ "$pr_state" != "open" ]; then + echo "::error::PR #$PR_NUMBER is not open." + exit 1 + fi + if [ "${COMMENT_AUTHOR,,}" != "${pr_author,,}" ]; then + commenter_permission="$( + gh api "repos/$REPOSITORY/collaborators/$COMMENT_AUTHOR/permission" \ + --jq '.permission' 2>/dev/null || printf 'none' + )" + case "$commenter_permission" in + admin|write) ;; + *) + echo "::error::Only PR author @$pr_author or a user with write access can request Docker CI." + exit 1 + ;; + esac + fi + + echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT" + + - name: Create isaaclab-bot token + id: app-token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + with: + client-id: ${{ secrets.CHANGELOG_APP_CLIENT_ID }} + private-key: ${{ secrets.CHANGELOG_APP_PRIVATE_KEY }} + permission-pull-requests: write + + - name: Trigger Docker CI + env: + EXPECTED_HEAD_SHA: ${{ steps.authorize.outputs.head_sha }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + LABEL: ci:run-docker + PR_NUMBER: ${{ github.event.issue.number }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + + # GitHub resolves the head when it processes the label, so labeling a + # PR whose head has already moved on builds a revision nobody asked + # for. Skip the request instead of spending GPU minutes on it. + head_sha="$(gh api "repos/$REPOSITORY/pulls/$PR_NUMBER" --jq '.head.sha')" + if [ "$head_sha" != "$EXPECTED_HEAD_SHA" ]; then + echo "::error::PR #$PR_NUMBER moved from ${EXPECTED_HEAD_SHA:0:7} to ${head_sha:0:7} while this request was processed. Comment 'run-ci' again to test the current head." + exit 1 + fi + + endpoint="repos/$REPOSITORY/issues/$PR_NUMBER/labels" + gh api --method DELETE "$endpoint/$LABEL" --silent >/dev/null 2>&1 || true + gh api --method POST "$endpoint" -f "labels[]=$LABEL" --silent + gh api --method DELETE "$endpoint/$LABEL" --silent + + # A push can still land between the check above and GitHub processing + # the label; nothing can make those two steps atomic. Re-read the head + # so the mismatch is reported rather than silently testing a revision + # the requester never saw. + built_sha="$(gh api "repos/$REPOSITORY/pulls/$PR_NUMBER" --jq '.head.sha')" + if [ "$built_sha" != "$EXPECTED_HEAD_SHA" ]; then + echo "::error::PR #$PR_NUMBER moved to ${built_sha:0:7} as the label was applied, so Docker CI is testing that revision and not the requested ${EXPECTED_HEAD_SHA:0:7}. Comment 'run-ci' again to test the current head." + exit 1 + fi + + echo "Requested Docker CI for PR #$PR_NUMBER at ${EXPECTED_HEAD_SHA:0:7}." >> "$GITHUB_STEP_SUMMARY"