Skip to content

Cut 0.1.0.dev4 and add a two-workflow auto-tag-then-release pipeline#50

Merged
smaniches merged 4 commits into
mainfrom
claude/repo-audit-improvement-cxu5a8
Jul 6, 2026
Merged

Cut 0.1.0.dev4 and add a two-workflow auto-tag-then-release pipeline#50
smaniches merged 4 commits into
mainfrom
claude/repo-audit-improvement-cxu5a8

Conversation

@smaniches

@smaniches smaniches commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Two things, in order.

Why: the process broke last time

0.1.0.dev3 was cut in source control by #48, but the tag push that should have followed never happened — my sandboxed session cannot push tags (the git proxy returns 403; only the designated branch push is authorized). Then #49 merged substantial new code (optimizer moment-transport fix, pipeline integration, a preregistered benchmark) on top of that un-tagged commit. Net result: two weeks of real changes on main, never released, never versioned, never deposited to Zenodo.

1. Cut 0.1.0.dev4 (63d001f)

Folds both un-released changesets into one honest release, since 0.1.0.dev3 never separately shipped:

  • Version bumped everywhere it's declared: pyproject.toml, groupoid/__init__.py, codemeta.json, CITATION.cff, STATUS.md (both mentions).
  • .zenodo.json: version bumped, erratum text extended to describe both the dev3 and dev4 content and to note dev3's non-release. isNewVersionOf already correctly pointed at dev2's real Zenodo DOI (10.5281/zenodo.20787126), since dev3 has none to chain from.
  • CHANGELOG.md: Unreleased rolled into [0.1.0.dev4]; the existing [0.1.0.dev3] section is kept for the historical record with a pointer forward, and its compare link repointed at the merge commit (af42cb4) since no tag exists for it.

2. Automate the step that broke — as two single-purpose workflows (e265379, revising a4f2199)

An earlier version of this PR put the auto-tagging logic inside release.yml itself, triggered on both tag-push and push-to-main, with a detect job gating the rest of that same run. Caught in review before merge: GitHub does not re-trigger a workflow from a push made with that run's own GITHUB_TOKEN, so it would not actually have double-published — but that design made the publish path's safety depend on an anti-recursion behavior instead of on visible workflow structure, which is the wrong thing to lean on for something that publishes to a public registry. Reworked into two workflows:

  • .github/workflows/release.yml is reverted to exactly its pre-automation form — byte-identical to before this PR. Tag-push and workflow_dispatch only; no branch trigger, no detect job. It does what it always did: build, provenance/SBOM attestation, PyPI publish, Sigstore signing, GitHub Release.
  • .github/workflows/auto-tag-release.yml is new and does exactly one thing: on a push to main that touches pyproject.toml, it checks whether the version's tag already exists; if not, it validates the version has a recognized release format (X.Y.Z, optionally .devN/aN/bN/rcN, no local-version metadata) and that CHANGELOG.md documents it, then creates and pushes the tag (as github-actions[bot]) and stops. Nothing else. That tag push is what triggers release.yml, exactly as a manual git tag && git push would.

The result: the actual publish path has exactly one trigger (a tag push) to reason about, structurally — not "two triggers that happen not to double-fire because of how GITHUB_TOKEN works." A push to main with no version change, or an already-tagged version, is a no-op in the tagging workflow and never reaches release.yml at all.

Deliberately not automated: the pypi GitHub Environment gate is untouched. If you have required reviewers configured there, publishing still waits for that approval — this closes the "forgot to tag" gap, not the "should a human approve this publish" decision, which stays yours.

CONTRIBUTING.md documents the resulting two-workflow release process.

3. CI resilience: retry the flaky torch install (a78dfb6)

Unrelated to the release automation, but discovered blocking this PR: two consecutive CI runs both failed identically across lint, test (3.10/3.11), security, benchmark, and docs — every one of them hit SSLV3_ALERT_HANDSHAKE_FAILURE against download-r2.pytorch.org (the CDN behind PyTorch's CPU wheel index) after pip's own internal retries were exhausted. This is a pre-existing step in ci.yml/docs-deploy.yml, untouched by this PR's other changes, and the failure is external (confirmed by hitting the identical error on a full rerun).

Extracted the install into .github/scripts/install_torch_cpu.sh: five attempts with linear backoff (10s/20s/30s/40s), each attempt a completely fresh pip install invocation (a fresh TLS connection — meaningfully different from pip's own internal retries, which reuse one connection pool within a single process), explicit error and exit 1 if all attempts are exhausted rather than silently falling through to a partial environment. All five occurrences of the raw install line across ci.yml and docs-deploy.yml now call this script instead. Verified locally: all three paths (immediate success, delayed success, exhausted failure) exercised directly against a stubbed pip/sleep, each producing the expected exit code.

Important: merging this PR fires a real release

Because 0.1.0.dev4's tag doesn't exist yet, the merge commit itself will trigger auto-tag-release.yml → push v0.1.0.dev4release.yml's full pipeline (build, attest, publish to PyPI pending your environment approval if configured, sign, release, and the resulting Zenodo deposit). This is intentional — it's exactly what clears the current backlog — but flagging it so it's not a surprise.

Verification

  • 101 tests, 100% line + branch coverage maintained
  • ruff, black, mypy --strict, bandit, pre-commit run --all-files, mkdocs build --strict, python -m build (produces groupoid-0.1.0.dev4.tar.gz/.whl): all green
  • .zenodo.json/codemeta.json parse as valid JSON; CITATION.cff parses as valid YAML; all workflow files parse as valid YAML with the expected jobs
  • The tagging workflow's shell logic (version extraction, format check, existing-tag check, changelog check) dry-run locally against the actual repo state: passes, correctly identifies v0.1.0.dev4 as not yet existing
  • git diff against the pre-automation commit confirms release.yml is byte-identical to before this PR

claude added 3 commits July 6, 2026 17:30
v0.1.0.dev3 was cut in source control by #48 but never tagged or
released -- no v0.1.0.dev3 tag was ever pushed, so #49's optimizer
moment-transport fix, pipeline integration, and preregistered benchmark
landed on main without ever being versioned. This cut folds both
changesets into a single honest release: PyPI, the GitHub Release, and
the Zenodo deposit all skip directly from 0.1.0.dev2 to 0.1.0.dev4.

- pyproject / __init__ / codemeta / CITATION / STATUS / .zenodo.json -> 0.1.0.dev4
- .zenodo.json erratum extended to describe the dev3 and dev4 content
  and to note dev3's non-release; isNewVersionOf already pointed at
  dev2's real Zenodo DOI (10.5281/zenodo.20787126), since dev3 has none
- CHANGELOG: Unreleased rolled into [0.1.0.dev4]; the existing
  [0.1.0.dev3] section is kept for the record with a pointer forward,
  and its compare link is repointed at the merge commit since no tag
  exists for it

Verified: 101 tests pass at 100% line+branch coverage; pre-commit
(ruff, black, mypy --strict, bandit) clean; mkdocs build --strict and
python -m build produce groupoid-0.1.0.dev4.
The release pipeline required a manual 'git tag vX.Y.Z && git push origin
vX.Y.Z' after a release-cut PR merged, and that step had no automation
and no reminder -- which is exactly how 0.1.0.dev3 (#48) shipped real
code changes on main for two weeks without ever being tagged, released,
or deposited to Zenodo, until #49 landed on top of it un-versioned too.

release.yml now also triggers on push to main. A new detect job reads
the version out of pyproject.toml; if no v<version> tag exists yet, it
creates and pushes one (as github-actions[bot]) and the rest of the
workflow proceeds exactly as it would from a manual tag push: build,
provenance/SBOM attestation, PyPI publish, Sigstore signing, GitHub
Release. A push to main with no version change is a no-op, since the
tag already exists. Tag pushes and workflow_dispatch keep their existing
behavior unchanged (detect short-circuits to should_release=true without
touching pyproject.toml for those).

The pypi GitHub Environment gate is untouched: if it has required
reviewers configured, the publish step still waits for that approval.
This automates the tagging step only, not the human publish decision.

CONTRIBUTING.md documents the resulting release process end to end.
The previous commit (a4f2199) added a 'detect' job inside release.yml
that both created the release tag (on a push to main) and, in the same
run, continued straight into build/publish/sign/release. Reviewed before
merge: GitHub does not re-trigger a workflow from a push made with the
run's own GITHUB_TOKEN, so that design would not have double-published
in practice -- but it made the publish path's safety depend on that
anti-recursion behavior rather than on visible workflow structure, which
is the wrong thing to rely on for something that publishes to a public
registry.

release.yml is reverted to exactly its pre-automation form: tag-push and
workflow_dispatch only, no detect job, no branches:[main] trigger. It is
now byte-identical to the version before a4f2199.

A new .github/workflows/auto-tag-release.yml does the tagging and
nothing else: on a push to main that touches pyproject.toml, it checks
whether v<version> already exists and creates it if not, after verifying
the version has a recognized release format (X.Y.Z, optionally
.devN/aN/bN/rcN, no local-version metadata) and that CHANGELOG.md
documents it. That tag push is what triggers release.yml, exactly as a
manual 'git tag && git push' would -- the actual publish path now has
exactly one trigger (a tag push) to reason about, structurally, not by
relying on token-recursion semantics.

CONTRIBUTING.md's Releasing section is updated to describe the two-
workflow split.
@smaniches smaniches changed the title Cut 0.1.0.dev4 and automate tag-and-release on version bumps Cut 0.1.0.dev4 and add a two-workflow auto-tag-then-release pipeline Jul 6, 2026
Two consecutive CI runs on this PR (an initial run and a rerun of the
failed jobs) both failed identically across lint, test (3.10/3.11),
security, benchmark, and docs: pip installing torch from
https://download.pytorch.org/whl/cpu hit
SSLV3_ALERT_HANDSHAKE_FAILURE against download-r2.pytorch.org (the
Cloudflare R2-backed CDN behind that index) after pip's own 5 internal
retries were exhausted. Unrelated to this PR's diff -- every failing job
shares this same pre-existing install step.

pip's built-in retries reuse the same connection pool within one
process, which does not help against a handshake failure on that
connection; a verified-locally property of this fix is that each retry
here is a completely fresh pip install invocation (a fresh TLS
connection), which is a meaningfully different retry strategy, not just
'try again and hope'.

Extracts the install into .github/scripts/install_torch_cpu.sh (five
attempts, linear backoff: 10s/20s/30s/40s, explicit error annotation and
exit 1 if all attempts are exhausted -- never silently falls through to
a partial environment) and calls it from every one of the five
occurrences of this install step across ci.yml and docs-deploy.yml,
replacing the raw pip install call verbatim; nothing else in any of
those steps changes.

Verified: the script's three paths (immediate success, delayed success,
exhausted failure) exercised directly with a stubbed pip/sleep, each
producing the expected exit code and log output; all workflow YAML
files still parse; the full local gate (pytest at 100% coverage, ruff,
black, mypy --strict, bandit, pre-commit) is unaffected, since none of
this touches groupoid/ or tests/.
@smaniches smaniches marked this pull request as ready for review July 6, 2026 21:31
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@smaniches smaniches merged commit 23d8918 into main Jul 6, 2026
11 checks passed
@smaniches smaniches deleted the claude/repo-audit-improvement-cxu5a8 branch July 6, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants