Skip to content

Tag last when cutting a release - #48

Merged
wtholliday merged 3 commits into
mainfrom
fix-release-tag-ordering
Aug 27, 2026
Merged

Tag last when cutting a release#48
wtholliday merged 3 commits into
mainfrom
fix-release-tag-ordering

Conversation

@wtholliday

Copy link
Copy Markdown
Collaborator

Fixes #37.

The bug

The release workflow triggered on release: created, so the tag already existed when the job started. It built the xcframework, uploaded it to that release, then pushed the Package.swift URL/checksum update to main afterwards. The tag never moved, so every tagged commit references the previous release's artifact:

0.32 -> download/0.31/
0.33 -> download/0.32/
0.34 -> download/0.33/
0.35 -> download/0.34/

b4b8b2f "Update Package.swift for release 0.35" is not an ancestor of tag 0.35 — it is the commit right after it, on main only.

This stayed harmless for many releases because the N-1 binary happened to export everything the Swift wrapper called. 0.35 broke it: #35 added lyte_program_has_entry_point to both Sources/CLyte/include/lyte.h and Sources/Lyte/Lyte.swift:127 in the same release. CLyte is a pure binaryTarget, so the header the Swift target compiles against comes from the downloaded xcframework, not from Sources/CLyte/include. Tag 0.35 downloads the 0.34 xcframework, whose header predates the symbol.

Consumers resolving by branch (as Audulus does) never saw this, because main always has the corrected Package.swift. The reporter is the first to pin by version.

The fix

Cut releases via workflow_dispatch with a version input instead of by creating a release in the UI, and create the tag last:

build -> update Package.swift on main -> gh release create "$TAG" --target <that commit sha> -> verify

gh release create --target creates the tag, the release, and uploads the asset at the commit that already names the right URL and checksum.

Guards

  • Version must be digits/dots, and the tag and release must not already exist — fails in seconds rather than after a ~30 minute LLVM build.
  • Both sed rewrites are verified with grep -q. A silently non-matching pattern would otherwise tag a Package.swift pointing at the wrong artifact.
  • The push retries with pull --rebase (3x), since main can move during the build. The commit is skipped entirely if Package.swift is already correct.
  • The final step clones the fresh tag into a clean directory and runs swift build. That is exactly the consumer's build path, so an issue Lyte 0.35 Swift package fails to build: missing lyte_program_has_entry_point in bundled CLyte artifact #37 class mismatch now fails the release job instead of reaching users.

Note

This only fixes future releases. Tag 0.35 stays broken for version-pinned consumers; cutting 0.36 from main (the first run of this workflow) unblocks them, and its verify step would prove the tag is good.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DeNzkFkzMbdH3XPzitDmVa

wtholliday and others added 3 commits August 26, 2026 17:50
The release workflow triggered on `release: created`, so the tag already
existed when the job started. It built the xcframework, uploaded it to
that release, then pushed the Package.swift URL/checksum update to main
afterwards. The tag never moved, so every tagged commit referenced the
*previous* release's artifact:

    0.32 -> download/0.31/
    0.33 -> download/0.32/
    0.34 -> download/0.33/
    0.35 -> download/0.34/

That stayed harmless while the N-1 binary happened to export everything
the Swift wrapper called. 0.35 broke it: the same release added
lyte_program_has_entry_point to both lyte.h and Lyte.swift, and since
CLyte is a pure binaryTarget the header comes from the downloaded
xcframework. Tag 0.35 downloads the 0.34 artifact, whose header predates
the symbol, so the package fails to build for anyone resolving by
version rather than by branch (issue #37).

Cut releases via workflow_dispatch instead, and create the tag last:
build -> update Package.swift on main -> create the release and tag at
that commit -> verify.

Also:

- Fail fast if the version is malformed or the tag/release already
  exists, rather than after a ~30 minute LLVM build.
- Check both sed rewrites landed; a silently non-matching pattern would
  otherwise tag a Package.swift pointing at the wrong artifact.
- Retry the push with a rebase, since main can move during the build.
- Verify by cloning the fresh tag and running swift build against it.
  That is the consumer's build path, so an issue #37 style mismatch now
  fails the release job instead of reaching users.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DeNzkFkzMbdH3XPzitDmVa
The README told you to cut a release with `gh release create`, which no
longer triggers anything — it would produce a release with no
xcframework attached.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DeNzkFkzMbdH3XPzitDmVa
Code review of the reordering found that it fixed the tag but broke the
branch. The Package.swift commit naming .../download/$TAG/... was pushed
to main *before* `gh release create` uploaded the asset, so a failure
between the two left main pointing at a 404 — and consumers resolving by
branch, which is how Audulus consumes this package, would fail to
resolve until someone reverted by hand. Verification also ran after the
release was already public, so the step whose entire purpose is to fail
would fail with a broken release published and the version burned.

Stage the Package.swift commit on a release/<version> branch instead.
The release and tag are created there, the published tag is verified,
and only then is main fast-forwarded onto it. If publishing or
verification fails, the release and tag are deleted so the version can
be re-run; main was never touched. A failure to fast-forward main leaves
a good release alone.

Also verify before publishing, not only after: build the package against
the freshly built xcframework via a local binaryTarget path. Against a
stale artifact this reproduces the issue #37 error exactly
("cannot find 'lyte_program_has_entry_point' in scope"), now with
nothing published.

Also:

- Abort if main moved during the build instead of rebasing onto it. The
  rebase would tag sources newer than the binary, which is the issue #37
  shape: merge a PR adding an FFI symbol mid-build and the tag ships a
  wrapper calling a symbol its artifact's header lacks.
- Require the version to look like 0.36 or 0.36.1. The old check only
  rejected empty and non-[0-9.] input, so a typo'd "036" would have
  produced a real tag and an unresolvable URL.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DeNzkFkzMbdH3XPzitDmVa
@wtholliday
wtholliday merged commit 0a45fd9 into main Aug 27, 2026
8 checks passed
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.

Lyte 0.35 Swift package fails to build: missing lyte_program_has_entry_point in bundled CLyte artifact

1 participant