Repository navigation
CI: Pixi workflow follow-ups after #2780 #2804
Description
Description
PR #2780 introduced Pixi lockfile freshness checks and automated, workspace-scoped refresh PRs. This issue collects the follow-up hardening and housekeeping identified during review.
Note
The freshness checks are intentionally advisory for now rather than merge-gating. That is desirable during the initial rollout: we should first observe their reliability, runtime, signal quality, canonicalization behavior, and scheduled refreshes. After the workflows have earned trust through normal use, revisit making freshness failures merge-gating. Before doing so, expose a stable aggregate check and account for the workflow's path filtering so required checks behave predictably on every PR.
Suggested follow-ups
Make blocking labels unconditional
Please close this gap: an explicit blocked or do not merge label should override every automation exemption.
pr-metadata-check.yml currently exits before reading labels for any bot-authored PR. The bot exemption is reasonable for positive metadata that automation cannot supply, such as an assignee or milestone, but it also prevents the check from enforcing the two blocking labels.
Split the validation into two parts:
- Always fetch and evaluate blocking labels for every non-draft PR, regardless of author.
- Continue exempting bot PRs from assignee, milestone, module-label, and type-label requirements as appropriate.
- Verify that bot PRs fail with either blocking label, pass without them, and recover when the blocking label is removed.
This keeps automated refresh PRs low-maintenance while preserving an explicit maintainer safety control.
Scope the refresh App token to the minimum permissions
The App token minted in ci-pixi-lockfile-refresh.yml currently omits the permission-* inputs, so it inherits every permission granted to the App installation. The job-level permissions block limits GITHUB_TOKEN; it does not narrow the separately minted App token.
Request only the permissions required by peter-evans/create-pull-request:
permission-contents: write
permission-pull-requests: writeAlso confirm that the App installation itself grants only the necessary repository access. The token action explicitly supports permission narrowing, and create-pull-request documents contents: write plus pull-requests: write for this use case:
- https://github.com/actions/create-github-app-token#inputs
- https://github.com/peter-evans/create-pull-request#token
Separate workspace identity from the refresh branch key
list_pixi_workspaces.py derives each workspace ID directly from its directory and the refresh workflow embeds that value in a Git branch. This leaves avoidable edge cases:
rootandallare reserved values but are not rejected as workspace directory names.- Parent/child IDs such as
fooandfoo/barmap to conflicting Git ref namespaces. - A valid repository path is not necessarily a valid Git ref component.
Keep the human-readable workspace ID, but validate reserved values and generate a separate deterministic, collision-resistant, ref-safe key for branch and concurrency names. Fail discovery with a clear error if the inventory cannot be represented safely.
Prevent the pinned Pixi version from drifting
PIXI_VERSION is independently repeated in the source-test, freshness, and refresh workflows. Centralize the version in one checked-in source if practical; otherwise add a lightweight parity check that fails when the three pins differ. Keep CONTRIBUTING.md pointing contributors to that canonical source.
General workflow housekeeping
Do a documentation and inline-comment pass over ci-pixi-lockfile-refresh.yml and CONTRIBUTING.md so the fallback path reflects current GitHub Actions behavior and configuration states:
- A PR opened or updated with
GITHUB_TOKENnow createspull_requestruns in an approval-required state foropened,synchronize, andreopened; it is no longer accurate to say that GitHub delivers nopull_requestevents at all. - Other event types remain suppressed, so explain why an App token is still preferable: it avoids the approval step and allows the full intended automation to run.
- Distinguish an intentionally absent App ID, which selects the
GITHUB_TOKENfallback, from a partial or invalid App configuration, which fails while minting the token rather than falling back. - Document the App installation prerequisites and least-privilege permissions next to the repository variable and secret names.
GitHub's current behavior is documented here:
- https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow#triggering-a-workflow-from-a-workflow
- https://github.blog/changelog/2026-06-11-bot-created-pull-requests-can-run-workflows-if-approved/
Completion criteria
- Blocking labels are enforced for bot-authored PRs without imposing the normal positive-metadata requirements on them.
- The refresh App token is explicitly least-privileged.
- Workspace selection IDs and generated branch keys are validated, unique, and ref-safe.
- The three workflow Pixi pins cannot drift silently.
- Refresh-workflow comments, generated PR text, and contributor guidance describe current token behavior and failure modes accurately.
- After sufficient operational experience, the team records whether and how to make freshness failures merge-gating.
Activity
rwgk commentedon Sep 12, 2026
Operational testing exposed a deterministic Pixi 0.73.0 canonicalization bug.
The bug in one sentence:
pixi updateandpixi lock --checkdisagree about the canonical representation of the same resolved lockfile. With no repository inputs changing, the lockfile alternates reproducibly between two forms:I.e., despite being invoked with
--check, the command rewrites the lockfile while returning success. Once the file is in form B, anotherpixi lock --checkis byte-stable. The problem is that the nextpixi updatechanges it back to form A, so an automated refresh PR cannot satisfy the freshness check that follows it.This is what happened in practice. A refresh job used Pixi 0.73.0 and ran:
It wrote form A and created PR #2839. After the PR's workflow runs were approved, the freshness job ran
pixi lock --check. That command returned success but rewrote the file to form B, which the subsequent byte-diff guard correctly detected.I reproduced the exact cycle with the official Pixi 0.73.0 binary at the refresh job's base commit, c280d47:
Form A exactly matches PR #2839, and form B exactly matches the CI rewrite. The PR head and Actions merge commit have identical trees, ruling out approval, merge-commit, checkout-depth, and timing effects. Earlier automated refresh PRs #2825 and #2835 exhibited the same self-failure.
No resolved package versions change. The relevant differences are the source-record identifier and the order of
cuda-cudartversuscuda-nvrtc. Pixi 0.73.0's source-record identifier hashes the dependency sequence in its stored order. The evidence therefore points to the update and lock-read/re-solve paths emitting different dependency orders for the same source package.The repository-side workaround is to run the same
pixi lock --checkcanonicalization immediately after eachpixi updatein the refresh workflow, before creating the PR, and then run it once more with a byte-stability assertion. This makes refresh PRs satisfy the existing freshness guard.Addresses the Pixi workflow follow-ups in #2804 (#2847)
rwgk commentedon Sep 15, 2026
To close the loop here, I created prefix-dev/pixi#7000.