ci+bleep: publish dev builds from CI, and resolve them from a snapshot version - #632
Merged
Conversation
The native images CI already builds are downloadable but unusable on their own.
A bleep binary asks Coursier for `build.bleep:bleep-bsp` at the version baked
into it, and that version — derived from this run's git state — was never
published anywhere. Client, server and test-runner must match, so a downloaded
binary resolves nothing.
`publish-local` already runs in `build`, for the dev script's own benefit. This
keeps its output. `constants.DefaultRepos` puts `~/.ivy2/local` ahead of Maven
Central, so unpacking the artifact there is all a downloaded binary needs:
gh run download <run-id> -n bleep-jars -D /tmp/j
cp -R /tmp/j/build.bleep ~/.ivy2/local/
gh run download <run-id> -n bleep-x86_64-apple-darwin -D /tmp/b
chmod +x /tmp/b/bleep && /tmp/b/bleep compile
Only this run's version is copied, never the whole of `~/.ivy2/local`. That
directory holds whatever the runner accumulated, and on a dev machine it is
worse — mine has 31 versions of bleep-bsp_3, 453MB, including released ones.
Shipping that would put jars nobody built here into an artifact people unpack
over their own ivy cache. Selecting on the version, which carries the commit
sha, yields exactly what this build produced: 15 modules, 15MB. The step fails
if a module the client needs is missing, or if anything from another version
rides along — both are silent until first use otherwise.
Staged with the shell rather than pointing `upload-artifact` at a `~` path,
which it does not expand and would silently match nothing.
Retention on the binaries goes 1 -> 7 days. One day was right when the only
consumer was the `release` job in the same run; it is no use to someone trying a
build before it ships. Seven still expires. Storage is free and unmetered on
public repositories, so this costs nothing but tidiness.
`prepare-release.sh` names each artifact directory explicitly, so the extra one
is ignored — it already coexists with the bsp-diagnostics-* artifacts.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
`~/.ivy2/local` accumulates — every version ever published or resolved on that machine. Mine holds 31 versions of bleep-bsp_3, 453MB, including released ones. Anything that wants to hand one build to someone else needs somewhere clean to put it, and filtering that directory afterwards is a worse answer than not mixing them in the first place. `Target.LocalIvy` carries an optional path; None keeps the existing behaviour, which is also what `constants.DefaultRepos` searches ahead of Maven Central. `--to` is wired onto the local-ivy subcommand only. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Uses the new `publish local-ivy --to`, so the artifact holds this build and nothing else by construction rather than by filtering `~/.ivy2/local` — which is whatever the runner accumulated, and which people are asked to unpack over their own ivy cache. The dev script rather than `bleep`: `--to` is new in this build, so the released binary on PATH does not have it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
oyvindberg
force-pushed
the
ci/dev-build-artifacts
branch
from
July 31, 2026 08:06
d89ec13 to
c6df0b7
Compare
Every other publish in this workflow passes `--version` from the generated BleepVersion; this one relied on dynver at runtime, which appends a timestamp when the working tree is dirty. The jars are only useful paired with a binary that names them, so the version cannot be left to depend on the tree being clean. Also ignores the staging directory, which is untracked output. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
A version in bleep.yaml that names a release is a URL — `releases/download/ v$version/bleep-$arch.tar.gz` — and FetchBleepRelease just builds it. A snapshot has no release, so `$version: 1.0.0-M10+83-fbe31c09-SNAPSHOT` failed with a 404 and no explanation. It now resolves from the artifacts of the run for its commit, which is what #632 started uploading. Measured rather than assumed: listing a public repository's artifacts is anonymous (HTTP 200), downloading one is not (HTTP 401). There is no anonymous path, so a snapshot cannot be made to "just work" the way a release does. Token from GITHUB_TOKEN, GH_TOKEN, or `gh auth token`; without one the error says so and says how to fix it, rather than surfacing a 401. The lookup goes commit -> run -> that run's artifacts. The obvious shape — `?name=<artifact>` filtered by commit — is quietly wrong: that endpoint pages, and it already returns a full page of 100 for a single name in this repository, so a snapshot whose run has fallen off page one would report "not found" while its artifact sat there unexpired. Going via the run is exact and one listing covers both artifacts. The extra `/commits/{sha}` call is because `?head_sha=` matches only a full 40-character sha while the version carries eight — verified against the API, a short sha returns zero runs. The jars are fetched too, not just the binary. Client, server and test-runner must be the same version, so a binary alone starts and then cannot resolve its own server. They are unpacked into `~/.ivy2/local`, which `constants.DefaultRepos` searches ahead of Maven Central. Routed inside FetchBleepRelease rather than at the call sites, so both `Main.maybeRunWithDifferentVersion` and `BleepExecutable` get it. The sha regex tolerates the dirty-tree shape `+82-c6df0b78+20260731-1016-SNAPSHOT`, which turns up whenever the working tree is not clean — taking everything after the dash would have produced a sha no run ever had. That case has a test because I hit it three times while building this. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Downloading the bleep-jars artifact this branch produced turned up two things that would each have broken snapshot resolution on exactly the artifacts we are publishing. **The version had no tag in it.** `VERSION: 0.0.0+1-53645ab8-SNAPSHOT`. The default depth-1 checkout fetches no tags, so dynver finds none and falls back to 0.0.0 — which is what the jars and every native image were published under, and what a bleep.yaml would have had to name to ask for one. `fetch-depth: 0` on both checkouts that publish. **The sha named a commit with no run.** For a pull_request event `actions/checkout` checks out `refs/pull/N/merge`, so the build derives its version from an ephemeral merge commit — 53645ab, `Merge fbe31c0 into 082c384` — while the run is recorded against the branch head. Looking up the merge commit finds the commit and then zero runs, confirmed against the API. GitHub writes those parents as [base, head], so the resolver retries with the second when the direct lookup comes back empty. Reached only then, so a push build — every master build and every release — never depends on it. Both were found by fetching the artifact rather than trusting that a green build meant a usable one. It is structurally fine: 15 modules, one version, VERSION matching what is inside, all four modules the client names present. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
`status == "completed"` was doing work it cannot do. A run cancelled by `concurrency.cancel-in-progress` also reads completed, with conclusion cancelled, and may have uploaded some artifacts and not others — observed here, where a superseded run held bleep-jars and one of the five binaries. So the run is chosen rather than taken: prefer completed with conclusion success, then any completed, then the newest. A commit can have several runs, and only a successful one is certain to have uploaded everything. And when an artifact is genuinely absent, the error says why the build did not produce it — still running, cancelled and probably superseded, or failed before uploading. Each is a different thing for the reader to do, and "artifact not found" was the same sentence for all of them. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The native images CI already builds are downloadable but unusable on their own. A bleep binary asks Coursier for
build.bleep:bleep-bspat the version baked into it, and that version — derived from the run's git state, e.g.1.0.0-M10+81-236bd0c5-SNAPSHOT— was never published anywhere. Client, server and test-runner must be the same version, so a downloaded binary resolves nothing and fails at first use.Almost nothing was missing.
publish-localalready runs in thebuildjob for the dev script's own benefit, andconstants.DefaultReposalready puts~/.ivy2/localahead of Maven Central. This just keeps the output.Using a dev build
Only this run's version
Uploading
~/.ivy2/local/build.bleepwholesale would have been wrong, and measurably so — my own copy holds 31 versions ofbleep-bsp_3, 453 MB, including released ones. That directory is whatever the machine accumulated, and people are being asked to unpack this artifact over their own ivy cache.Selecting on the version, which carries the commit sha, gives exactly what this build produced:
The step fails loudly if a module the client needs is absent, or if anything from another version rides along. Both are silent until first use otherwise.
Staged with the shell rather than pointing
upload-artifactat a~path — the action does not runpaththrough a shell, so a leading~is not guaranteed to expand and would silently match nothing.Retention
Binaries go 1 → 7 days. One day was correct when the only consumer was the
releasejob in the same run; it is no use to someone trying a build before it ships. Seven days still expires. The repo is public, so Actions storage is free and unmetered — this costs tidiness, not money.Rough footprint: ~200 MB of binaries + 15 MB of jars per run, held 7 days.
Not broken
prepare-release.shnames each artifact directory explicitly, so the extrableep-jarsis ignored — it already coexists with thebsp-diagnostics-*artifacts, which proves the pattern.Open question
This runs on every branch, since a PR build is exactly when you want to try a binary before merging. Restricting to master is a one-line
if:on the two steps if the artifact count is unwelcome.🤖 Generated with Claude Code