Repository navigation
Conversation
uv takes its interpreter from .python-version (3.12) before the one setup-python puts on PATH, so the py3.13 leg ran the unit suite on 3.12. UV_PYTHON outranks the file, and each leg now checks what it got before testing (F144). Every action is pinned by commit SHA with its version in a comment, checkouts keep no credentials, and the workflow defaults to a read-only token (F141). A new `workflow lint` job runs pinned actionlint and zizmor over every workflow, since the tag-only ones never run on a pull request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
publish.yml had no permissions block, so every job got the repository's default write token, and a TestPyPI dispatch from a tag also published to PyPI, as did a PyPI dispatch from a branch (F142). Each job now names its permissions; PyPI needs a pushed tag, or a PyPI dispatch from a tag, and the version check, which runs for tags alone. quay-on-tag.yml ran on every tag by itself, so a tag whose tests failed or that named the wrong version still pushed and moved :latest (F143). It is now a reusable workflow that publish.yml calls once PyPI has the release. It builds the image, checks it runs (help, the version, a non-root user, duckdb, convert and view as the caller on a read-only root), and only then pushes. No layer cache from earlier runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The benchmark job held contents: write while it installed the newest anndata, scanpy, dask and geopandas from PyPI and ran them (F084). It now measures with a read-only token, no uv cache and only the lock for adata-cli itself, and a second job, which installs nothing, renders the results with the standard library and benchmarks/ from main and commits them. The tag and the tier reach scripts through env: (F141). "vs previous" took the stored run with the newest mtime, which in a fresh checkout is whichever file git wrote last: 0.17.0 was compared with 0.8.0 (F085). It is now the release just before, ordered by version as numbers, at the same tier; a run that is not a release compares with the newest release. The page names the run it compares with. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The base was the floating python3.12-bookworm-slim, duckdb was unzipped into /usr/local/bin unchecked, and the image ran as root (F138). The base is pinned by its index digest (the same image the floating tag names today, uv 0.9.30), each duckdb zip is checked against its SHA-256 (taken from the zips as both GitHub and install.duckdb.org serve them), and the image runs as uid 10001. Singularity, Apptainer and Docker with -u still run it as the caller; the pipelines guide says to pass --user when Docker writes to a bind mount. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/test_release_pipeline.py reads the workflows and the Dockerfile and checks what matters at the next tag: which jobs publish in each kind of run (worked out from each job's needs and if:, as GitHub evaluates them), that a job holding contents: write installs nothing, that no script interpolates a tag, input or output, that every action is pinned, that each Python leg runs its own Python, and that the image is built from pinned inputs and runs unprivileged. Against main's files, 19 of its 28 cases fail. AGENTS.md's Releases section, developers.md and testing.md describe the new flow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…ep credentials An unused loop variable in benchmark.yml and an unchecked cd in the fresh-install job. The benchmark's commit job keeps its checkout credentials to push to main, and says so to zizmor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
B7's floors job came in with main; it gets the same pins and checkout setting as every other job, which tests/test_release_pipeline.py checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the recovered audit's suggested fixes: - F143: :latest moved for any tag without a hyphen, which takes in 1.0.0rc1 and a backport tagged after a later release. It now moves only when the version is the highest X.Y.Z tag; a test runs the step's script against a stand-in for git ls-remote. - F141: setup-uv installed the newest uv at run time. Every workflow now names uv 0.13.1, so a release uses the uv this suite ran on. - Each regression test's docstring names its ref. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the independent review of #110: - zizmor's only findings were its self-repository suggestion for the two `./` reusable-workflow calls in publish.yml, which exited 12 and would have failed the release's test job. Ignored inline: actionlint 1.7 does not know the `$/` form. - The lint job no longer runs inside a release: zizmor's online audits would let a new advisory, or GitHub's API, stop one. - A new `container image` job in tests.yml builds the image and runs .github/scripts/smoke-image.sh on every pull request, and inside publish.yml's `test`, so an image that does not build or run stops a release before PyPI has it. quay-on-tag.yml runs the same script and pushes the image it checked, not a rebuild; the smoke files no longer land in the build context. - The :latest step sets pipefail: GitHub's default `bash -e` let a failed ls-remote pass as "not the newest". - check-version refuses a PyPI dispatch from a branch, rather than the run passing having published nothing. twine is pinned. - A benchmark dispatch from a tag commits only when asked to. - The F085 oracle test no longer fails on a run named by its commit. - New tests: the check-version and :latest scripts run under bash -e against stand-ins, a called workflow's permissions against its caller's grant, the benchmark commit condition, the lint and image jobs per event. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
testing.md: keep both sides' rows in the suite map. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes #102. Part of #87. For 0.17.5: merge after 0.17.4 is tagged.
The workflows test what they say, publish only what passed, and hold no token they don't need. main (with B7) is merged in, and B7's
floorsjob has the same pins.Per finding
F144 (
tests.yml): the unit job setsUV_PYTHONfrom the matrix, which outranks.python-version(3.12). A step then prints the interpreter and fails if it isn't the leg's. Checked:F143 (
quay-on-tag.yml,tests.yml,.github/scripts/smoke-image.sh): the image is pushed only after the tests, the version check and the PyPI upload have passed.quay-on-tag.ymlis now only a reusable workflow, called bypublish.ymlafterpublish-pypi.--help, that--versionequals the release, a non-root uid, duckdb, andconvertcsv→h5ad→zarr plusviewas the caller on a read-only root.container imagejob intests.ymlbuilds the image and runs the same script on every PR. It also runs inside Publish'stest, so an image that doesn't build or run stops the release before PyPI has it.:latestmoves only when the version is the highestX.Y.Ztag, so not for1.0.0rc1or a backport.Checked:
test_release_pipeline.pyevaluates each job'sneeds/if:(table below).:latestscript under GitHub'sbash -eagainst a stand-ingit, including a failing lookup.F142 (
publish.yml):permissions: {}at the top, and each job asks for its own. The reusabletestgetschecks/pull-requests: write, and a test checks that every called job fits within its caller's grant.publish-pypineedscheck-versionandgithub.event_name == 'push' || inputs.target == 'pypi'.check-versionalso runs for a PyPI dispatch, and refuses a branch.Checked: the scenario table, and a test that runs
check-version's script for a tag, a mismatched tag and a branch.F084 (
benchmark.yml): two jobs.benchmarkmeasures withcontents: read, keeps no checkout credentials, uses no uv cache, and installs adata-cli withuv sync/uv run --frozen.publish(contents: write) installs nothing. It rendersresults.jsonwith the runner'spython3(stdlib +benchmarks/from main), commits, and edits the release notes.publish, and only after a measurement.Checked: a bare CPython 3.12 runs
benchmarks.report --out/--publishon a copy ofdocs/.test_a_job_that_can_push_installs_nothingcovers every workflow, and the commit condition is tested per event.F141 (all workflows):
env:.Checked: tests, and zizmor (no impostor-commit, ref-version-mismatch or advisory findings).
F138 (
Dockerfile):uv:0.9.30-python3.12-bookworm-slim@sha256:e5b6558…. That is the image the floating tag names today, so the base doesn't change;sha256sum -c. I took the hashes from the zips as GitHub and install.duckdb.org serve them, and the two agree;USER 10001:10001.Checked: both arches build (
/tmp/duckdb.zip: OK), a wrong hash fails the build, and nowcontainer imagebuilds the image on every PR.F085 (
benchmarks/report.py): "vs previous" is now the release just before, by version and at the same tier. A run that isn't a release compares with the newest release, and the page names the run it compares with. Checked:test_every_published_run_compares_with_the_release_before_itchecks every run indocs/benchmarks/againstpackaging.version, including when a dispatch's<sha>.jsonis in the store.Publishing, run by run
From
test_each_run_publishes_exactly_what_passed:testpypifrom a tagpypifrom a tagtestpypifrom a branchpypifrom a branch:latest)Against main's workflows, Dockerfile and
report.py, 40 of the new and changed tests fail (17 pass, among them the cases where main already published nothing).Behaviour changes
/home/adata. Singularity, Apptainer and Docker with-u $(id -u):$(id -g)(as nf-core sets) still run it as the caller.docker run -v "$PWD":/data …now needs--user "$(id -u):$(id -g)"if it writes to the mount. The same applies to reads, if your files aren't world-readable.runOptions.docs/guides/pipelines.mdsays so.USERline restores root.container imagejob: it is part oftests.yml, which Publish runs. Theworkflow lintjob is skipped inside a release.test.:latestfollows the highestX.Y.Ztag; a backport or a pre-release no longer takes it. The pushed image no longer carries buildx's provenance attestation, because it is pushed withdocker push.benchmark-results, and CI uses uv 0.13.1. The image keeps its base's uv 0.9.30.Independent review
A fresh subagent checked each workflow on a PR, a push to main, a tag, and each dispatch, and confirmed:
What it raised, and what I did:
workflow lintwas red and would block the release. zizmor's only findings wereself-repository(use$/for./reusable workflows) on two lines ofpublish.yml, which exits 12. Fixed: an inline# zizmor: ignore[self-repository], since actionlint 1.7 doesn't know$/.smoke/landed in the build context. Fixed: the build runs once and the checked image is pushed. The smoke data lives in$RUNNER_TEMP.publishwould break the F085 oracle test on main (Version("8d779d6")). Fixed, with a test.tests.yml, before PyPI.bash -ewithout pipefail let a failedls-remoteleave:latestbehind silently. Fixed:set -o pipefail. The test now runs underbash -e, with a failinggit.Nits:
import yamlinstead ofimportorskip;docs/changelog.md/STATUS.md: the plan's rule is that the release PR collects entries from this description.container imagenow builds the image for real on every PR.Considered, kept
The benchmark page is rendered with main's
report.pyandpage_template.md, not the tag's. The page lives on main, and its wording is edited there (AGENTS.md says so). Rendering with the tag's template would undo wording changes made after the tag.Only the next tag (0.17.5) can confirm
publish.ymland thepypienvironment are unchanged.docker pushof0.17.5andlatest.publish-unit-test-result-actionwriting its check run undertest's grant.publishjob pushing to main and editing the release notes.No tag was pushed and no Publish, Benchmark or image workflow was dispatched.
Follow-ups (not done here)
github-actionsanddocker. The pinned actions run on Node 20, which GitHub is retiring, so new majors will be needed.bookworm-slimhasn't been updated since Feb 2026; moving totrixieis its own change.write, and thepypienvironment has no protection rules.results.jsonis rendered into the docs page. Pinning them, or escaping the rendered strings, would close that.Changelog entry
Fixed
py3.13CI leg runs Python 3.13 (#102). uv took its interpreter from.python-versionbefore the one onPATH, so the leg ran the suite on 3.12. Each leg now setsUV_PYTHONand checks what it got.:latesteven when tests failed or the tag named the wrong version. It is now built and smoke-tested with the tests, before PyPI, and pushed after PyPI has the release.:latestmoves only to the newest final version.env:.Changed
-ustill run it as the caller. A plaindocker runon a bind mount needs--user "$(id -u):$(id -g)".🤖 Generated with Claude Code