Skip to content
Merged
6 changes: 6 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Runner labels actionlint cannot know about. Without this, every workflow using a
# Depot runner is an `unknown label` error — which matters now that
# lint-release-workflows.yml lints dojo-e2e.yml at `fail_level: error`.
self-hosted-runner:
labels:
- depot-ubuntu-24.04
58 changes: 58 additions & 0 deletions .github/actions/assert-lockfiles-unchanged/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Assert lockfiles unchanged
description: >-
Fails the job if any step modified a committed lockfile. This is the direct check
that CI never silently repairs lockfile drift instead of reporting it.

# Why this is an outcome check rather than a rule about which commands ran:
# an earlier version of this change had a CI script that read the shell inside every
# `run:` block looking for a uv command that might rewrite a lockfile. Three review
# rounds found new shell shapes that slipped past its regexes — `$(uv sync)`,
# `$((1<<n))` read as a heredoc opener, `shell: python` bodies scanned as bash — so
# that approach was abandoned in favour of measuring the thing itself. A lockfile
# that changed during the job is the property it was trying to infer, and git
# answers that directly.
#
# Run this LAST in a job. `uv sync --locked` refuses to rewrite in the first place,
# so on a healthy job this asserts what already holds; it earns its keep when a step
# is added that syncs without `--locked`.
#
# NOT used by jobs that sync the `examples/` apps: five of those lockfiles are
# knowingly stale and dojo-e2e rewrites them on purpose. See the `lockfiles` job
# comment in .github/workflows/unit-python-sdk.yml.

runs:
using: composite
steps:
- name: Assert no committed lockfile was modified
shell: bash
run: |
set -euo pipefail

# Refuse to pass vacuously. If the pathspec matches nothing the assertion
# below is trivially true, which is the one outcome a guard must never
# silently produce.
tracked=$(git ls-files -- '*uv.lock' '*poetry.lock' | wc -l | tr -d ' ')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verification table lists "shellcheck (scripts/release/*.sh, composite action) — clean". That's true of your machine, but CI lints neither half of this file:

  • the shellcheck job globs scripts/release/*.sh, which doesn't reach .github/actions/**
  • reviewdog's actionlint gets an explicit workflow file list via actionlint_flags, so composite action definitions are out of scope there too

So the file carrying the most new shell in this PR is the one file nothing checks. actionlint does lint composite actions when pointed at them — adding .github/actions/assert-lockfiles-unchanged/action.yml to the actionlint_flags list, or .github/actions/**/*.yml to the shellcheck job, closes it.

The logic itself looks right to me, for what it's worth — I confirmed .gitignore:28 (**/python/**/.venv/) covers every .venv these jobs create, so the git status pathspec won't pick up venv noise, and the vacuous-pass guard is a good call.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, but not the way you suggested — actionlint can't lint composite actions. 1.7.x has no composite-action mode. Pointed at this file it parses it as a workflow:

action.yml:1:1: "jobs" section is missing in workflow [syntax-check]
action.yml:1:1: "on" section is missing in workflow [syntax-check]
action.yml:2:1: unexpected key "description" for "workflow" section [syntax-check]
action.yml:23:1: unexpected key "runs" for "workflow" section [syntax-check]

So adding it to actionlint_flags would have turned the actionlint job red with four bogus errors and linted nothing. I tried it with a deliberately injected SC2086 to check whether the shell got scanned at all — it doesn't.

Your other suggestion is the workable one, and it needs the shell to be in a file first. The check now lives in assert-lockfiles-unchanged.sh and action.yml invokes it via $GITHUB_ACTION_PATH; the shellcheck job globs .github/actions alongside scripts/release. Logic unchanged. 12 files, clean.

Two things that fell out of doing it:

  • The job used shopt -s nullglob + a scripts/release/*.sh array. I first reached for .github/actions/**/*.sh, then found globstar is bash 4 and this repo gets developed on macOS where /bin/bash is 3.2 — the glob silently matches nothing there. mapfile has the same problem. It's find + xargs -0 now, which runs identically in 3.2 and 5, so the job can actually be verified locally rather than only in CI — which was your underlying point about my table.
  • It also exited 0 when the glob matched nothing. A lint job that passes by checking nothing is the same vacuous-pass failure you credited the action for guarding against, so that's now a hard failure with an error message.

You're right that "composite action — clean" in the table was a local result. The table now marks what was verified how, and this file is genuinely in CI's shellcheck scope rather than only on my machine.

Also confirmed your .gitignore:28 finding independently — **/python/**/.venv/ does cover every .venv these jobs create, so the pathspec stays clean.

if [ "$tracked" -eq 0 ]; then
echo "::error::No lockfiles are tracked at $(pwd) — this check would pass by" \
"inspecting nothing. Is the repository checked out?"
exit 1
fi

# git status, NOT git diff. `git diff` compares worktree against the INDEX and
# only reports tracked files, so it missed the two likeliest ways a lockfile
# changes: a step that staged it (`git add`), and a NEW lock created where none
# existed — which is exactly what `uv sync` in sdks/python/a2ui_toolkit, the one
# first-party package with no committed lock, would produce. Both printed
# "unchanged". `--porcelain` reports modified, staged and untracked in one pass.
changed=$(git status --porcelain -- '*uv.lock' '*poetry.lock')
if [ -n "$changed" ]; then
echo "::error::A step in this job modified a committed lockfile:"
echo "$changed" | sed 's/^/ /'
echo
echo "CI must not repair lockfile drift. Run the equivalent command locally," \
"commit the updated lockfile, and push it."
git --no-pager diff --stat -- '*uv.lock' '*poetry.lock'
exit 1
fi

echo "All $tracked tracked lockfile(s) unchanged."
45 changes: 45 additions & 0 deletions .github/python-toolchain.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Pinned Python build toolchain for CI.
#
# This file records the uv and CPython versions every Python job in .github/workflows/
# is expected to use, and the green run they came from.
#
# It is documentation, not a mechanism. GitHub cannot read a file into a workflow's
# `env:` block, so each Python workflow repeats whichever of these two values it
# actually uses, and NOTHING IN CI CHECKS THAT THEY STILL MATCH — keeping them in step
# is on whoever edits a workflow, and on review. An earlier version of this change
# shipped a checker for it; it cost far more than the drift it prevented and was

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the main blocker for me.

The removed checker is cited as the reason no check exists, but it's a strawman for what's actually needed here. Verifying that 17 literals equal two values is not a workflow analyser — it never has to parse run: blocks, resolve expressions, or model job structure. It's a grep, and the lockfiles job already exists and already runs on .github/** changes:

set -euo pipefail
. .github/python-toolchain.env
found=$(grep -rhoE '^  (UV_VERSION|PYTHON_VERSION): "[^"]+"' .github/workflows | sort -u)
expected=$(printf '  PYTHON_VERSION: "%s"\n  UV_VERSION: "%s"' "$PYTHON_VERSION" "$UV_VERSION")
if [ "$found" != "$expected" ]; then
  echo "::error::workflow pins disagree with .github/python-toolchain.env"
  diff <(echo "$expected") <(echo "$found") || true
  exit 1
fi

I ran the equivalent against this branch — 17/17 agree today, so this lands green. That's the moment to add it; it only ever goes red on the drift this file exists to prevent.

There's also a genuine single-source-of-truth option the "GitHub cannot read a file into env:" framing rules out too quickly: a small setup job that reads the file into job outputs. ${{ needs.setup.outputs.uv_version }} is valid in with: and in cache keys, which is where both values are actually consumed — the env: limitation is real but not binding. I'd still take the grep over nine extra needs: edges, but the file shouldn't record the stronger claim.

As it stands the PR applies a weaker standard to its own invariant than the one it imposes on lockfiles two files over, and the reasoning for that asymmetry is recorded in a way that discourages revisiting it.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — you were right, and the strawman diagnosis was correct. scripts/release/verify-python-toolchain-pins.sh, running as the python-toolchain-pins job.

Two differences from your snippet, both from testing it:

It also checks pass 2. Comparing declarations only sees pins that exist. A newly added setup-uv that names no version declares nothing to compare and passes silently — which is precisely the state all 18 invocations were in before this PR. So it also asserts uses: astral-sh/setup-uv@ count equals version: ${{ env.UV_VERSION }} count. Three lines, catches the regression that matters most.

Quote-tolerant, and it reports file:line. UV_VERSION: 0.12.1 and UV_VERSION: "0.12.1" are the same pin; a check that understood one spelling would read the other as absent and pass over the drift. On failure it names each file and line rather than diffing two sorted blobs.

Hosted in lint-release-workflows.yml, not the lockfiles job. This is the one place your comment was off: unit-python-sdk.yml doesn't trigger on .github/**, it lists three specific paths (python-toolchain.env, its own file, .github/actions/**). A pin drifted in zizmor.yml would never have fired it. All nine pin-carrying workflows sit inside lint-release-workflows.yml's lint scope, and it already hosts three sibling guards of exactly this class — hand-maintained list vs. the file it mirrors. It also needs no uv.

Proved it goes red, not just green: drifted literal, unquoted-but-wrong pin, unpinned setup-uv, and renamed pins — 4/4 fail correctly, and a correct-but-single-quoted pin stays green. Both passes refuse to pass vacuously.

Writing it also caught a bug in my own first version: grep -r over .github/workflows with no --include picked up the .yml.bak files my tests left behind and reported 19/19 against a tree with 17 declarations and 18 invocations. A stray .yml.orig from a bad merge would have done the same in CI. Now filtered, and the reason is in the comment so nobody removes it.

On the setup-job point — you're right and the file overstated it. Job outputs are valid in with: and cache keys, which is where both values are consumed, so "GitHub cannot read a file into env:" is true but implies more than it should. The header now records that alternative and why the grep won: nine extra needs: edges, including onto the nine-lane matrix, to remove a duplication six lines already hold in place. If the needs: graph gets rearranged for other reasons, it says to revisit.

The "do not re-add a checker" framing is gone from the file. It was doing what you describe — discouraging revisiting a decision that deserved it.

One thing your comment prompted that's outside this PR: .node-version from #2325 has the identical gap, and CONTRIBUTING says so in as many words ("nothing enforces that automatically yet"). Filed as PNI-280 and cross-linked, since the mechanism now exists.

# removed deliberately, so please do not read the absence as an oversight.
#
# A workflow that needs only one value declares only that one — prepare-release.yml and
# lint-release-workflows.yml install no uv, so they carry PYTHON_VERSION alone; an
# unused copy of a pin is only somewhere for it to drift.
#
# Provenance: both versions are the ones a *green* run actually resolved, not a
# guess. Run https://github.com/ag-ui-protocol/ag-ui/actions/runs/30958253208
# (unit-python-sdk.yml, main @ bfc22e4e, 2026-08-04 — several workflows share the
# display name "unit", so the file name is what identifies it) installed uv 0.12.1 in
# every Python job ("Successfully installed uv version 0.12.1") and built every venv
# against "CPython 3.12.3 interpreter at: /usr/bin/python3".
#
# Three honest caveats about what this pin does and does not freeze:
# - Naming python-version makes setup-uv select a uv-MANAGED CPython rather than
# the runner's /usr/bin/python3. Same minor version as the run above, different
# provenance. That is the intended trade: a declared interpreter beats whatever
# the runner image ships.
# - PYTHON_VERSION is minor-precision, so 3.12.x patch releases still resolve at
# run time and share one cache key. uv recreates a venv whose interpreter moved,
# so the cost is a cache HIT whose contents are then discarded and rebuilt —
# slower than a plain miss, but not a red build. Set a full patch version here to
# close it; either precision works.
# - Naming python-version also exports UV_PYTHON for the whole job, so EVERY later uv
# invocation in it is constrained to this interpreter — including the example-app
# syncs prep-dojo-everything.js performs during dojo-e2e, which previously resolved
# per project. Every requires-python in the repo admits 3.12 today, so nothing
# breaks; a future example pinned to >=3.13 would fail inside dojo-e2e rather than
# in the package that declared it.
#
# To move the pin: pick the versions from a newer green run, update this file, update
# the `env:` block of every workflow that declares them (grep for UV_VERSION under
# .github/workflows/), and record the new run id above.
UV_VERSION=0.12.1
PYTHON_VERSION=3.12
35 changes: 33 additions & 2 deletions .github/workflows/build-python-preview.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
name: Build Python Preview

# Version-rewriting flow: "Rewrite pyproject.toml versions" below stamps a
# 0.0.0.devN version into each package listed in PACKAGES in
# scripts/rewrite-python-preview-versions.py before building, which puts those
# pyprojects deliberately out of step with their committed uv.lock. That list is a
# subset of the Python packages in scripts/release/release.config.json — read the
# script for the current set rather than trusting a count here.
#
# Audited for lockfile safety, and the finding is that nothing here consumes or
# writes a lockfile: `uv build` neither reads nor rewrites uv.lock, and the rewrite
# script runs with --no-project. To re-audit after a uv bump: stale a lockfile
# (bump its package's version in pyproject.toml only), run `uv build` in that
# directory, and confirm uv.lock is byte-identical afterwards.
#
# Because nothing here reads a lockfile, this workflow does NOT run
# .github/actions/assert-lockfiles-unchanged — there is no sync step for it to
# guard. Add it alongside the first step that does sync.
#
# The toolchain pins below come from .github/python-toolchain.env like every other
# Python workflow's.

on:
pull_request:
types: [opened, synchronize, reopened]
Expand All @@ -11,6 +31,14 @@ concurrency:
permissions:
contents: read

# Pinned Python build toolchain — see .github/python-toolchain.env, which is the
# single place these two values are recorded, along with the green run they came
# from. Keeping them equal across workflows is a convention, not an enforced check:
# change one, change them all.
env:
UV_VERSION: "0.12.1"
PYTHON_VERSION: "3.12"

jobs:
build:
runs-on: ubuntu-latest
Expand All @@ -26,7 +54,8 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: ">=0.8.0"
version: ${{ env.UV_VERSION }}
python-version: ${{ env.PYTHON_VERSION }}

- name: Compute preview version
id: version
Expand All @@ -37,7 +66,9 @@ jobs:
echo "Preview version: ${VERSION}"

- name: Rewrite pyproject.toml versions
run: uv run python scripts/rewrite-python-preview-versions.py ${STEPS_VERSION_OUTPUTS_VERSION}
# --no-project: the script is stdlib-only and there is no project at the
# repo root, so this states outright that no lockfile is consulted.
run: uv run --no-project python scripts/rewrite-python-preview-versions.py "${STEPS_VERSION_OUTPUTS_VERSION}"
env:
STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }}

Expand Down
32 changes: 28 additions & 4 deletions .github/workflows/dojo-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ on:
- "sdks/python/**"
- "sdks/typescript/**"
- "sdks/dotnet/**"
- ".github/python-toolchain.env"
pull_request:
branches: [main]
paths:
Expand All @@ -26,10 +27,19 @@ on:
- "sdks/python/**"
- "sdks/typescript/**"
- "sdks/dotnet/**"
- ".github/python-toolchain.env"

permissions:
contents: read

# Pinned Python build toolchain — see .github/python-toolchain.env, which is the
# single place these two values are recorded, along with the green run they came
# from. Keeping them equal across workflows is a convention, not an enforced check:
# change one, change them all.
env:
UV_VERSION: "0.12.1"
PYTHON_VERSION: "3.12"

jobs:
check-generated-files:
name: dojo / check-generated-files
Expand Down Expand Up @@ -211,7 +221,7 @@ jobs:
# Now that pnpm is available, cache its store to speed installs
- name: Resolve pnpm store path
id: pnpm-store
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"

- name: Cache pnpm store
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
Expand All @@ -229,10 +239,15 @@ jobs:
~/.cache/pypoetry
~/.cache/uv
**/.venv
key: ${{ steps.fork-check.outputs.prefix }}${{ runner.os }}-pydeps-${{ matrix.suite }}-${{ hashFiles('**/poetry.lock', '**/pyproject.toml') }}
# The toolchain segments keep a venv built by one uv/Python from being
# restored into a job expecting another — including via restore-keys,
# which would otherwise reach past them on a key miss. Note the limit:
# this path also captures Poetry-built venvs, and Poetry is installed at
# `version: latest` below, so a Poetry upgrade is NOT reflected in the key.
key: ${{ steps.fork-check.outputs.prefix }}${{ runner.os }}-pydeps-py${{ env.PYTHON_VERSION }}-uv${{ env.UV_VERSION }}-${{ matrix.suite }}-${{ hashFiles('**/poetry.lock', '**/pyproject.toml') }}
restore-keys: |
${{ steps.fork-check.outputs.prefix }}${{ runner.os }}-pydeps-${{ matrix.suite }}-
${{ steps.fork-check.outputs.prefix }}${{ runner.os }}-pydeps-
${{ steps.fork-check.outputs.prefix }}${{ runner.os }}-pydeps-py${{ env.PYTHON_VERSION }}-uv${{ env.UV_VERSION }}-${{ matrix.suite }}-
${{ steps.fork-check.outputs.prefix }}${{ runner.os }}-pydeps-py${{ env.PYTHON_VERSION }}-uv${{ env.UV_VERSION }}-

- name: Cache Next.js build
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
Expand All @@ -249,8 +264,17 @@ jobs:
virtualenvs-create: true
virtualenvs-in-project: true

# uv itself is only invoked indirectly here — the example apps under
# integrations/*/python/examples are synced by
# apps/dojo/scripts/prep-dojo-everything.js. Those syncs are deliberately
# not frozen (the script is shared with local dev, where relocking is
# wanted), so this pin is what keeps the interpreter and resolver stable
# across e2e runs.
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: ${{ env.UV_VERSION }}
python-version: ${{ env.PYTHON_VERSION }}

- name: Set up .NET SDK
if: ${{ contains(join(matrix.services, ','), 'microsoft-agent-framework-dotnet') || contains(join(matrix.services, ','), 'ag-ui-dotnet') }}
Expand Down
55 changes: 37 additions & 18 deletions .github/workflows/lint-release-workflows.yml
Original file line number Diff line number Diff line change
@@ -1,39 +1,52 @@
name: Lint Release Workflows

# Runs actionlint + shellcheck against the release / create-pr, release /
# publish, and canary / publish pipelines and the scripts they call. Keeps
# these critical, retry-sensitive files from silently regressing on shell or
# action-syntax bugs.
# Runs actionlint + shellcheck against the release, canary, and Python CI
# pipelines and the scripts they call. Keeps these critical, retry-sensitive files
# from silently regressing on shell or action-syntax bugs.
#
# Scope is intentionally narrow: only the release workflows and
# scripts/release/*. Expanding later is cheap; starting narrow avoids
# drowning unrelated changes in pre-existing lint noise.
# The actionlint file list below is explicit rather than repo-wide, so that adding
# a workflow does not drown an unrelated PR in pre-existing lint noise — read that
# list, not this comment, for the current scope. Everything else under
# .github/workflows/ is unlinted; widening is a one-line change plus whatever it
# turns up. shellcheck covers scripts/release/*.sh only.


on:
push:
branches: [main]
paths:
- ".github/workflows/prepare-release.yml"
- ".github/workflows/publish-release.yml"
- ".github/workflows/canary.yml"
- ".github/workflows/lint-release-workflows.yml"
- "scripts/release/**"
- ".github/workflows/**"
- ".github/actionlint.yaml"
- "scripts/**"
- "nx.json"
pull_request:
paths:
- ".github/workflows/prepare-release.yml"
- ".github/workflows/publish-release.yml"
- ".github/workflows/canary.yml"
- ".github/workflows/lint-release-workflows.yml"
- "scripts/release/**"
- ".github/workflows/**"
- ".github/actionlint.yaml"
- "scripts/**"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two problems with widening the triggers while the lint scope stays explicit.

The widening buys nothing on its own. actionlint_flags below is still a hand-listed set of workflows, and shellcheck still globs scripts/release/*.sh. So a PR touching scripts/foo.ts or an unlisted workflow now spins up both jobs to lint exactly the same files as before. Either widen the lint scope to match the triggers, or keep the triggers at the files that actually affect the result plus the six new workflows.

It changes the fork-PR story. With reporter: github-check, reviewdog creates a check run — and on pull_request from a fork the GITHUB_TOKEN is read-only no matter what the new checks: write says, so that call fails. Previously this workflow almost never fired on fork PRs because the paths were five internal release files. With scripts/** in the list, an external contributor touching scripts/ now trips it. Worth either gating the job on github.event.pull_request.head.repo.full_name == github.repository or switching the reporter for the fork case.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Narrowed the triggers to match the lint scope, which fixes both halves at once.

paths: is now the ten linted workflows + .github/actions/assert-lockfiles-unchanged/action.yml + .github/actionlint.yaml + .github/python-toolchain.env + scripts/release/** + nx.json. No more runs that lint nothing new, and scripts/** is gone so an external contributor touching scripts/ no longer reaches the github-check reporter.

Chose narrowing over widening the lint scope because widening is the "one-line change plus whatever it turns up" the header already anticipates, and what it turns up is pre-existing noise across every unlinted workflow — which is what the narrow scope was protecting against. The cost is that the file list now lives in three places (two paths: blocks plus actionlint_flags); that's stated in the header, along with why a generated fourth list would be the wrong trade here.

On the fork case: narrowing makes it rare, not impossible. A fork PR editing one of the ten linted workflows still trips the reporter. I left that alone deliberately — it was already true for prepare-release.yml, publish-release.yml and canary.yml before this PR widened anything, so it's pre-existing rather than introduced here. Happy to add the head.repo.full_name == github.repository gate if you'd rather close it properly, but it felt like a separate change from undoing my own widening.

Writing this caught a bug I'd introduced in the fix itself, worth flagging since it's the kind that lints clean: I first put the "not .github/actions/**" note as a # comment inside the actionlint_flags: >- folded block scalar. # isn't a comment there — it's literal text. The flags string parsed to 73 arguments, 63 of them prose fragments being passed to actionlint. actionlint on the workflow was clean because the YAML is valid; only parsing the value exposed it. Comment moved above the key, with a line warning that nothing below it may be a comment, and I now assert the value parses to exactly 10 existing paths.

- "nx.json"

permissions:
contents: read

# Pinned CPython — see .github/python-toolchain.env, which records this value and the
# green run it came from. Keeping it equal across workflows is a convention, not an
# enforced check.
#
# No UV_VERSION here: this workflow installs no uv, and an unused copy of the pin is
# only somewhere for it to drift.
env:
PYTHON_VERSION: "3.12"

jobs:
actionlint:
runs-on: ubuntu-latest
permissions:
contents: read
# reviewdog's github-check reporter creates the check run that carries the
# annotations; without it, findings are enforced by exit code alone and nothing
# visible says why the job failed.
checks: write # create the check run reviewdog reports annotations through
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -49,6 +62,12 @@ jobs:
.github/workflows/publish-release.yml
.github/workflows/canary.yml
.github/workflows/lint-release-workflows.yml
.github/workflows/unit-python-sdk.yml
.github/workflows/dojo-e2e.yml
.github/workflows/build-python-preview.yml
.github/workflows/publish-python-preview.yml
.github/workflows/zizmor.yml
.github/workflows/test-release-scripts.yml

shellcheck:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -110,6 +129,6 @@ jobs:
- name: Setup Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
python-version: ${{ env.PYTHON_VERSION }}
- name: Verify config package names match manifests
run: bash scripts/release/verify-config-manifest-names.sh
14 changes: 11 additions & 3 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ permissions:

env:
NX_VERBOSE_LOGGING: true
# Pinned Python build toolchain — see .github/python-toolchain.env, kept in step
# across workflows by convention, not by an enforced check. UV_VERSION is needed
# here because prepare-release.ts re-locks the packages it bumps (#2314), so this
# workflow does run uv.
UV_VERSION: "0.12.1"
PYTHON_VERSION: "3.12"

jobs:
create-release-pr:
Expand Down Expand Up @@ -130,15 +136,17 @@ jobs:
- name: Setup Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
python-version: ${{ env.PYTHON_VERSION }}

# prepare-release.ts re-locks any uv-managed Python package it bumps, so uv
# must be on PATH or the bump aborts rather than shipping a stale lock.
# Same pinned action and floor as unit-python-sdk.yml.
# The exact version matters here more than anywhere: this is the uv whose
# output lands in a committed lockfile.
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: ">=0.8.0"
version: ${{ env.UV_VERSION }}
python-version: ${{ env.PYTHON_VERSION }}

- name: Install dependencies
run: pnpm install --frozen-lockfile
Expand Down
Loading
Loading