Skip to content

Commit 35380ca

Browse files
authored
Addresses the Pixi workflow follow-ups in #2804 (#2847)
* Addresses the Pixi workflow follow-ups in #2804: * Enforce blocking labels on bot-authored PRs while retaining their positive-metadata exemptions. * Restrict refresh App tokens to required permissions and document fallback behavior. * Generate validated, ref-safe refresh branch keys separately from workspace IDs. * Centralize the pinned Pixi version. * Canonicalize refreshed lockfiles and verify byte stability before opening PRs. * Add REFRESH_WORKFLOW_URL to error message. * ci: address pixi workflow review feedback * No-op refreshes no longer emit a false warning.
1 parent 47ef05c commit 35380ca

9 files changed

Lines changed: 274 additions & 123 deletions
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
name: Setup pinned pixi
6+
description: Install the repository's canonical pixi version without installing an environment
7+
8+
outputs:
9+
pixi-version:
10+
description: The canonical pixi version installed by this action
11+
value: ${{ steps.version.outputs.pixi-version }}
12+
13+
runs:
14+
using: composite
15+
steps:
16+
- name: Read canonical pixi version
17+
id: version
18+
shell: bash --noprofile --norc -euo pipefail {0}
19+
run: |
20+
source "${GITHUB_WORKSPACE}/ci/pixi-version.env"
21+
if [[ ! "${PIXI_VERSION:-}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
22+
echo "::error::ci/pixi-version.env must set PIXI_VERSION in vMAJOR.MINOR.PATCH form"
23+
exit 1
24+
fi
25+
echo "pixi-version=${PIXI_VERSION}" >> "${GITHUB_OUTPUT}"
26+
27+
- name: Setup pixi ${{ steps.version.outputs.pixi-version }}
28+
# Pinned to a commit SHA; install logic lives in the action and is
29+
# auditable/pinned (vs. a curl|bash of an unverified installer).
30+
uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
31+
with:
32+
pixi-version: ${{ steps.version.outputs.pixi-version }}
33+
run-install: false

‎.github/workflows/ci-pixi-lockfile-freshness-check.yml‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
# Remediation for both: regenerate and commit the lockfile with that pixi
88
# version, e.g.
99
# pixi lock --manifest-path <path>
10+
# Keep this check advisory during its initial rollout. Before making it
11+
# required, expose a stable aggregate check, account for the path filters, and
12+
# record the decision in #2804 after the workflow has operational history.
1013
# See #2298.
1114

1215
name: "CI: pixi lockfile freshness check"
@@ -28,6 +31,8 @@ on:
2831
- "**/pixi.lock"
2932
- "**/pyproject.toml"
3033
- "ci/tools/list_pixi_workspaces.py"
34+
- "ci/pixi-version.env"
35+
- ".github/actions/setup-pixi/action.yml"
3136
- ".github/workflows/ci-pixi-lockfile-freshness-check.yml"
3237
push:
3338
# `pull_request` already covers PRs, including those from forks: this check
@@ -43,9 +48,7 @@ defaults:
4348
shell: bash --noprofile --norc -xeuo pipefail {0}
4449

4550
env:
46-
# Keep in sync with ci-pixi-source-test.yml. Must be >=0.71.0 so the
47-
# committed lockfile format (v7) matches what this job checks against.
48-
PIXI_VERSION: "v0.73.0"
51+
REFRESH_WORKFLOW_URL: "https://github.com/NVIDIA/cuda-python/actions/workflows/ci-pixi-lockfile-refresh.yml"
4952

5053
permissions: {}
5154

@@ -92,17 +95,13 @@ jobs:
9295
persist-credentials: false
9396

9497
- name: Setup pixi
95-
# Pinned to a commit SHA; install logic lives in the action and is
96-
# auditable/pinned (vs. a curl|bash of an unverified installer).
97-
uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
98-
with:
99-
pixi-version: ${{ env.PIXI_VERSION }}
100-
run-install: false
98+
id: setup-pixi
99+
uses: ./.github/actions/setup-pixi
101100

102101
- name: Check lockfile is current with its manifest
103102
run: |
104103
if ! pixi lock --check --manifest-path "${{ matrix.manifest }}"; then
105-
echo "::error::Lockfile is stale for '${{ matrix.manifest }}'. Regenerate with: pixi lock --manifest-path ${{ matrix.manifest }}"
104+
echo "::error::Lockfile is stale for '${{ matrix.manifest }}'. Regenerate with: pixi lock --manifest-path ${{ matrix.manifest }}. For default-branch dependency drift, maintainers can run ${REFRESH_WORKFLOW_URL}"
106105
exit 1
107106
fi
108107
@@ -112,8 +111,10 @@ jobs:
112111
# is invisible: the check prints "Updated lock file" and still passes,
113112
# while every later pixi run keeps rewriting the committed file (#2298).
114113
- name: Check lockfile is byte-for-byte canonical
114+
env:
115+
PIXI_VERSION: ${{ steps.setup-pixi.outputs.pixi-version }}
115116
run: |
116117
if ! git diff --exit-code -- "${{ matrix.lockfile }}"; then
117-
echo "::error::pixi ${PIXI_VERSION} rewrote ${{ matrix.lockfile }} during the check, so the committed bytes are not what it generates. Regenerate with pixi ${PIXI_VERSION}: pixi lock --manifest-path ${{ matrix.manifest }}"
118+
echo "::error::pixi ${PIXI_VERSION} rewrote ${{ matrix.lockfile }} during the check, so the committed bytes are not what it generates. Regenerate with pixi ${PIXI_VERSION}: pixi lock --manifest-path ${{ matrix.manifest }}. For default-branch dependency drift, maintainers can run ${REFRESH_WORKFLOW_URL}"
118119
exit 1
119120
fi

‎.github/workflows/ci-pixi-lockfile-refresh.yml‎

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@ defaults:
2828
run:
2929
shell: bash --noprofile --norc -xeuo pipefail {0}
3030

31-
env:
32-
# Keep in sync with ci-pixi-source-test.yml.
33-
PIXI_VERSION: "v0.73.0"
34-
3531
permissions: {}
3632

3733
jobs:
@@ -71,7 +67,7 @@ jobs:
7167
contents: write
7268
pull-requests: write
7369
concurrency:
74-
group: pixi-lock-refresh-${{ matrix.id }}
70+
group: pixi-lock-refresh-${{ matrix.branch_key }}
7571
cancel-in-progress: false
7672
strategy:
7773
fail-fast: false
@@ -83,75 +79,75 @@ jobs:
8379
fetch-depth: 0
8480
ref: ${{ github.event.repository.default_branch }}
8581

86-
# GitHub suppresses workflow-triggering events for pushes and PRs made
87-
# with GITHUB_TOKEN, so a refresh PR opened with the default token cannot
88-
# reach a green required-check state on its own. An App token restores
89-
# normal event delivery; without one the PR needs a manual nudge, which
90-
# the PR body and a run warning spell out.
91-
- name: Mint App token for the refresh PR
92-
id: app-token
93-
if: ${{ vars.PIXI_LOCK_REFRESH_APP_ID != '' }}
94-
uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2
95-
with:
96-
app-id: ${{ vars.PIXI_LOCK_REFRESH_APP_ID }}
97-
private-key: ${{ secrets.PIXI_LOCK_REFRESH_APP_PRIVATE_KEY }}
98-
9982
- name: Setup pixi
100-
# Pinned to a commit SHA; install logic lives in the action and is
101-
# auditable/pinned (vs. a curl|bash of an unverified installer).
102-
uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
103-
with:
104-
pixi-version: ${{ env.PIXI_VERSION }}
105-
run-install: false
83+
id: setup-pixi
84+
uses: ./.github/actions/setup-pixi
10685

10786
- name: Update lockfile without installing
10887
run: pixi update --no-install --manifest-path "${{ matrix.manifest }}"
10988

89+
# Pixi 0.73.0's update and lock-read paths can serialize the same solve
90+
# differently (#2804). Canonicalize with the same operation used by the
91+
# freshness workflow before opening the PR.
92+
- name: Canonicalize the updated lockfile
93+
run: pixi lock --check --manifest-path "${{ matrix.manifest }}"
94+
95+
- name: Verify canonicalization is byte-stable
96+
env:
97+
LOCKFILE: ${{ matrix.lockfile }}
98+
MANIFEST: ${{ matrix.manifest }}
99+
PIXI_VERSION: ${{ steps.setup-pixi.outputs.pixi-version }}
100+
run: |
101+
canonical_blob="$(git hash-object "${LOCKFILE}")"
102+
pixi lock --check --manifest-path "${MANIFEST}"
103+
verified_blob="$(git hash-object "${LOCKFILE}")"
104+
if [ "${canonical_blob}" != "${verified_blob}" ]; then
105+
echo "::error::pixi ${PIXI_VERSION} rewrote ${LOCKFILE} on a second consecutive pixi lock --check; refusing to open an unstable refresh PR."
106+
exit 1
107+
fi
108+
110109
- name: Compose PR body
111110
id: pr-body
112111
env:
113112
WORKSPACE_ID: ${{ matrix.id }}
114113
MANIFEST: ${{ matrix.manifest }}
115-
HAS_APP_TOKEN: ${{ steps.app-token.outputs.token != '' }}
114+
PIXI_VERSION: ${{ steps.setup-pixi.outputs.pixi-version }}
116115
run: |
117116
body_path="${RUNNER_TEMP}/pixi-lock-refresh-body.md"
118117
{
119-
echo "Automated lockfile refresh for \`${WORKSPACE_ID}\`" \
120-
"(\`pixi update --no-install --manifest-path ${MANIFEST}\`)."
118+
echo "Automated lockfile refresh for \`${WORKSPACE_ID}\` using pixi" \
119+
"\`${PIXI_VERSION}\` (\`pixi update --no-install\`, followed by" \
120+
"byte-stable \`pixi lock --check\` canonicalization)."
121121
echo
122122
echo "This PR is workspace-scoped so other packages are not forced to review"
123123
echo "unrelated solver churn. See #2298."
124124
echo
125-
if [ "${HAS_APP_TOKEN}" != "true" ]; then
126-
echo "> [!IMPORTANT]"
127-
echo "> No refresh App token is configured, so this PR was opened with"
128-
echo "> \`GITHUB_TOKEN\` and GitHub will not deliver its \`pull_request\` events."
129-
echo "> Required checks stay pending until a maintainer nudges the branch, by"
130-
echo "> closing and reopening the PR or pushing an empty commit to it."
131-
echo
132-
fi
125+
echo "> [!IMPORTANT]"
126+
echo "> This PR was opened with \`GITHUB_TOKEN\`. A maintainer with write access must:"
127+
echo ">"
128+
echo "> 1. Select **Approve workflows to run** in the merge box."
129+
echo "> 2. Assign themselves to this PR. The human-generated \`assigned\` event"
130+
echo "> creates the required **PR has assignee, labels, and milestone** check"
131+
echo "> and gives the PR a clear owner."
132+
echo
133133
echo "If the diff looks wrong, close this PR and leave the branch deleted; the"
134134
echo "next scheduled run opens a fresh one if an update is still needed."
135135
} > "${body_path}"
136136
echo "path=${body_path}" >> "${GITHUB_OUTPUT}"
137137
138-
if [ "${HAS_APP_TOKEN}" != "true" ]; then
139-
echo "::warning::Opened with GITHUB_TOKEN; the refresh PR's checks must be triggered manually."
140-
fi
141-
142138
- name: Open or update a lockfile-only PR
143139
id: cpr
144140
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
145141
with:
146-
token: ${{ steps.app-token.outputs.token || github.token }}
142+
token: ${{ github.token }}
147143
add-paths: ${{ matrix.lockfile }}
148144
commit-message: |
149145
chore: refresh pixi.lock for ${{ matrix.id }}
150146
151147
Scheduled pixi update --no-install for this workspace only.
152148
signoff: true
153149
sign-commits: true
154-
branch: ci/pixi-lock-refresh/${{ matrix.id }}
150+
branch: ci/pixi-lock-refresh/${{ matrix.branch_key }}
155151
delete-branch: true
156152
title: "chore: refresh pixi.lock (${{ matrix.id }})"
157153
labels: |
@@ -161,6 +157,10 @@ jobs:
161157
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
162158
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
163159

160+
- name: Warn about required maintainer action
161+
if: ${{ steps.cpr.outputs.pull-request-operation == 'created' || steps.cpr.outputs.pull-request-operation == 'updated' }}
162+
run: echo "::warning::The refresh PR requires a maintainer to approve its workflow runs and assign themselves to create the required metadata check."
163+
164164
- name: Summarize
165165
run: |
166166
{

‎.github/workflows/ci-pixi-source-test.yml‎

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ on:
4747
- "cuda_bindings/tests/cython/**"
4848
- "cuda_core/tests/cython/**"
4949
- "ci/versions.yml"
50+
- "ci/pixi-version.env"
51+
- ".github/actions/setup-pixi/action.yml"
5052
- ".github/workflows/ci-pixi-source-test.yml"
5153
workflow_dispatch:
5254
inputs:
@@ -60,13 +62,6 @@ defaults:
6062
shell: bash --noprofile --norc -xeuo pipefail {0}
6163

6264
env:
63-
# keep in sync with the version developers run locally. Must be >=0.71.0:
64-
# older pixi re-ran the editable source build on every `pixi run`, recompiling
65-
# all Cython extensions (#2138). The fix (content-addressed source-build cache,
66-
# prefix-dev/pixi#6285 + #6123) also bumps the pixi.lock format to v7.
67-
# Keep in sync with ci-pixi-lockfile-refresh.yml and
68-
# ci-pixi-lockfile-freshness-check.yml.
69-
PIXI_VERSION: "v0.73.0"
7065
# Install from the committed lockfile only. Covers nested `pixi run` calls
7166
# from root pixi.toml tasks; a stale lock must fail rather than being
7267
# refreshed in CI (#2298). Equivalent to `pixi run --locked`.
@@ -91,12 +86,7 @@ jobs:
9186
fetch-depth: 0
9287

9388
- name: Setup pixi
94-
# Pinned to a commit SHA; install logic lives in the action and is
95-
# auditable/pinned (vs. a curl|bash of an unverified installer).
96-
uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
97-
with:
98-
pixi-version: ${{ env.PIXI_VERSION }}
99-
run-install: false
89+
uses: ./.github/actions/setup-pixi
10090

10191
- name: Source-build + import + cython-placement smoke
10292
env:
@@ -152,12 +142,7 @@ jobs:
152142
fetch-depth: 0
153143

154144
- name: Setup pixi
155-
# Pinned to a commit SHA; install logic lives in the action and is
156-
# auditable/pinned (vs. a curl|bash of an unverified installer).
157-
uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
158-
with:
159-
pixi-version: ${{ env.PIXI_VERSION }}
160-
run-install: false
145+
uses: ./.github/actions/setup-pixi
161146

162147
- name: Build cu13, then cu12, then cu13 again in one checkout
163148
run: |
@@ -217,12 +202,7 @@ jobs:
217202
continue-on-error: true
218203

219204
- name: Setup pixi
220-
# Pinned to a commit SHA; install logic lives in the action and is
221-
# auditable/pinned (vs. a curl|bash of an unverified installer).
222-
uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
223-
with:
224-
pixi-version: ${{ env.PIXI_VERSION }}
225-
run-install: false
205+
uses: ./.github/actions/setup-pixi
226206

227207
- name: pixi run test
228208
run: pixi run -e "${{ inputs.cuda-env || 'cu13' }}" test

0 commit comments

Comments
 (0)