Skip to content

ci(conda): publish release archives to the prefix.dev channel - #616

Merged
viniciusdc merged 6 commits into
mainfrom
feat/publish-conda-channel
Aug 27, 2026
Merged

ci(conda): publish release archives to the prefix.dev channel#616
viniciusdc merged 6 commits into
mainfrom
feat/publish-conda-channel

Conversation

@viniciusdc

@viniciusdc viniciusdc commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Closes

Closes #579. Moving to prefix.dev's shared github-releases channel and deleting this bridge is #620. Part of #552.

What this is

pixi installs conda packages. There is no other artifact type it consumes, so "install nic with pixi" and "a conda package for nic exists" are the same requirement, and this produces one.

It does not compile anything. rattler-build unpacks the archive GoReleaser already published and copies the binary into the prefix - three lines of build.script, no Go toolchain, no source tarball. The recipe is a repackaging manifest, not a build.

That is also what octoconda does for the shared channel: it generates a recipe with the same source: url + sha256 and the same binary_relocation: false / prefix_detection.ignore pair, then runs a generic extract-and-relocate script. The difference here is the digest. octoconda reads GitHub's API asset digest; this reads the sha256 from the release's checksums.txt after cosign-verifying that file against the release workflow's identity. Given the point is that the bytes a conda user runs are the bytes we signed, that is the half worth owning.

Why now

The starter workspaces merged in #594 already declare the dependency:

channels = ["conda-forge", "https://prefix.dev/github-releases"]
nebari-infrastructure-core = "__NIC_VERSION__"

Nothing carries it. Onboarding onto github-releases is a one-line entry in octoconda's config.toml, open upstream and not ours to merge. So this publishes to nebari-dev/nebari, a channel we control, and says plainly in packaging/conda/README.md what deleting it looks like.

Shape

Path Role
packaging/conda/recipe.yaml.tmpl The repackaging manifest, with placeholder slots
packaging/conda/build-packages.sh Verifies the signature, then renders and builds once per conda subdir
.github/workflows/publish-conda.yml Runs it on release, uploads over OIDC

One Linux runner emits all six subdirs. Copying files is not platform-bound; only the nic version smoke test is, and --test native runs it for the build platform and skips it elsewhere with a logged reason.

Separate from release.yml on purpose. The recipe fetches assets from an already-published release by URL, so it has to run after that release exists and be able to run for one cut long ago. workflow_dispatch from main backfills versions published before this workflow existed, and re-runs an upload that failed without cutting a release. That last part earns its keep: GitHub's /releases list endpoint returned empty for this repo for about a day earlier this month while every other endpoint kept working.

No long-lived token. Upload goes through prefix.dev trusted publishing over OIDC, bound to the release environment.

Stable releases only, enforced. The tag is checked against the release API and a prerelease refused, which covers the dispatch path a trigger filter would miss.

Two things that bite

conda forbids - in a version string. It separates name, version and build string in a package filename, so a v0.14.0-rc.1 tag builds an artifact whose own version cannot be resolved, surfacing as Error: Test failed / failed to setup test environment. Normalised to 0.14.0rc1.

Windows differs twice: .zip rather than .tar.gz, and binaries under Library/bin rather than bin.

How to Test

$ packaging/conda/build-packages.sh v0.14.0 dist/conda
==> verifying checksums.txt against the release workflow's identity
Verified OK
==> linux-64  <-  nebari-infrastructure-core_0.14.0_linux_x86_64.tar.gz  (2583d11313c4)
...six subdirs...

$ pixi init --channel "file://$PWD/dist/conda" --channel conda-forge /tmp/check
$ cd /tmp/check && pixi add nebari-infrastructure-core && pixi run nic version
Commit: c1aa182bd860088a758e10addccf6e2e948552d2
Built: 2026-08-25T13:52:46Z

The commit and build date are the release's, not dev, so the ldflags survive repackaging. A one-byte-tampered checksums.txt fails verification before any digest is read. actionlint, shellcheck and scripts/check-action-pins.sh all pass.

Scope and open items

Provider tools stay in the starter workspaces rather than this package. conda-forge publishes no opentofu for win-arm64, which NIC does ship a package for, and local/existing never invoke OpenTofu; #617 already scopes the constraint to the AWS starter and derives it from pkg/tofu. Recorded in docs/operations/packaging.md.

Repointing starters/templates/pixi.toml.tmpl at this channel is not here. It has to wait until the channel actually serves, which needs this merged, the trusted publisher registered, and a first publish.

--generate-attestation on the upload is deferred rather than declined: it would close the provenance chain past the channel boundary, but it cannot be tested until the trusted publisher is live, so it belongs on the first real dispatch.

Still unverified: the -c nebari-dev/nebari channel spelling. The first dispatch is the first real test; if it is wrong the fix is one line.

@viniciusdc
viniciusdc marked this pull request as ready for review August 25, 2026 20:24

@dcmcand dcmcand left a comment

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.

Verified the load-bearing claims rather than taking them on trust: the cosign check reproduces exactly with the pinned v3.1.1 (control Verified OK, one-byte-tampered manifest rejected), the rendered digests match v0.14.0's real checksums.txt for all six assets, and I built linux-64 and cross-built win-64 on Linux - the test ran and reported the release version and commit on the first, skipped with the documented cross-compilation message on the second, and the package contains Library/bin/nic.exe. Verifying the signature before reading any digest is the right ordering and the comment explaining why earns its place.

Two blockers, both small: the build number needs to be a substituted slot, and one sentence in packaging.md is false for win-arm64. Four suggestions inline, none gating.

On the open question - provider tools do not belong in the package. conda-forge has never published opentofu for win-arm64, across every version including the current 1.12.6, so an unconditional run dependency trades a working install for a missing one on a platform you otherwise cover. NIC is also provider-agnostic at the binary level and local/existing never touch OpenTofu. The workspace layer already handles this better than a package dependency could: #617 derives the AWS starter's constraint from pkg/tofu.MinVersion/MaxVersionExclusive, which is a tighter pin and scoped to the provider that needs it. The gap that leaves is Azure, and that belongs with whatever adds the Azure starter.

# of dying on the first already-published filename. It cannot detect
# different bytes under the same filename, which is why the build is
# reproducible from the signed release rather than from local state.
rattler-build upload prefix -c nebari-dev/nebari --skip-existing "$pkg"

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.

--skip-existing here plus the hardcoded number: 0 in recipe.yaml.tmpl:18 means a republish after a recipe fix uploads nothing and exits green.

The filename is name-version-<varianthash>_<buildnumber>.conda, and the variant hash covers the variant configuration, not the recipe body. I built the same source three ways:

recipe filename
as merged nebari-infrastructure-core-0.14.0-hb0f4dca_0.conda
extra line in build.script nebari-infrastructure-core-0.14.0-hb0f4dca_0.conda
requirements.run: [opentofu >=1.11.3,<2.0.0] added nebari-infrastructure-core-0.14.0-hb0f4dca_0.conda

So fixing a recipe bug and re-dispatching for a published tag hits six filenames the channel already has, skips all six by design, and reports success. The channel keeps serving the broken package.

That third row is this PR's own open question: deciding to add a provider-tool dependency would require republishing existing versions, and the republish would no-op. docs/operations/packaging.md:122 also sends the operator down exactly this path as troubleshooting step 4.

The comment above is the specific thing to correct - the package bytes are a function of the release archive and the recipe, and the recipe is mutable on main, so reproducibility from the signed release covers only half of it.

Fix: make the build number a substituted slot (__BUILD_NUMBER__, default 0) so a recipe change bumps it, or take --force on a deliberate republish path.

Unaffected: a tag's first publish, and resuming a genuinely partial upload. Both work as advertised.

Comment thread docs/operations/packaging.md Outdated
[https://github.com/nebari-dev/nebari-infrastructure-core/issues/552](https://github.com/nebari-dev/nebari-infrastructure-core/issues/552)),
and conda-forge ships `opentofu` for all platforms NIC supports, so a pixi
workspace that pins both never has to phone home on first run.
set `NIC_TOFU_PATH` in an activation script. conda-forge ships `opentofu` for all

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.

conda-forge does not ship opentofu for win-arm64. Its published subdirs are linux-64, linux-aarch64, linux-ppc64le, osx-64, osx-arm64, win-64 - across every version, including the current 1.12.6.

This PR ships a win-arm64 conda package (build-packages.sh:55, and nebari-infrastructure-core_0.14.0_windows_arm64.zip is in the release), so a packager following this guidance there gets an unsolvable workspace. The PR description already states the fact: "would also impose it on local users and make win-arm64 unsolvable, since conda-forge ships no opentofu there."

The reading that rescues the sentence is "platforms NIC supports" meaning the four in the starter templates rather than the six NIC publishes, but your own text does not use it that way. A clause naming win-arm64 as the exception fixes it.

GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./packaging/conda/build-packages.sh "$TAG" dist/conda

- name: Upload to prefix.dev

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.

Worth trying --generate-attestation (and --store-github-attestation) on the upload - rattler-build 0.74.0's upload prefix has both, and this job already declares id-token: write.

The provenance chain is airtight up to the channel boundary and stops there: you verify the release signature going in, but someone pulling nebari-infrastructure-core from prefix.dev has nothing to check. Given the argument for this whole approach is that the bytes a conda user runs are the bytes you signed, letting them verify that is the natural close.

I could not test these against your channel without the trusted-publisher setup, so this is worth trying on the first dispatch rather than adding blind.

Comment thread .github/workflows/publish-conda.yml Outdated
# The tag must name a real release. On the dispatch path no release
# event vouches for it, so this is also where stable-only is enforced
# - a `release: published` filter alone would let a dispatched rc through.
if ! prerelease="$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isPrerelease -q .isPrerelease 2>/dev/null)"; then

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.

2>/dev/null turns any gh failure - rate limit, token scope, outage - into no such release: $TAG.

The PR description notes GitHub's /releases endpoint returning empty for this repo for about a day. That is the exact scenario this line misdiagnoses, at the top of the workflow whose documented recovery is to re-run it. Capturing stderr and echoing it with the error costs two lines.

Comment thread .github/workflows/publish-conda.yml Outdated
# cutting a new release.
on:
release:
types: [published]

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.

types: [released] would stop release candidates before the approval prompt rather than after it.

.goreleaser.yml sets draft: false and prerelease: auto, so RC tags land as prereleases. With published, an RC starts a run, waits on the release environment approval, and only then fails the tag check at line 77. GitHub's docs point at published as the type to use when you want both stable and prerelease, so released is the stable-only one.

The API check at lines 73-80 still has to stay - it is the only thing covering the dispatch path.

resumes rather than failing on the first one. This is also how a release
published before the workflow existed gets backfilled.

This channel is a bridge. The intended home is prefix.dev's shared

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 sentence this PR correctly removed from line 82 still stands in two other places:

  • docs/adr/0016-opentofu-runtime-version-policy.md:19 - "NIC is distributed via the prefix.dev github-releases channel", word for word.
  • starters/templates/pixi.toml.tmpl:8 - "nic resolves from the prefix.dev github-releases channel; see the distribution issue for how that channel is populated." This one ships inside every generated starter.

Not reopening the scoping call on the template's channels list - waiting until the channel is confirmed serving is sound, and #620 may make github-releases correct for real. But that comment is wrong today independent of the decision, and it is the line a starter user reads while wondering why pixi install will not resolve. Worth a tracking issue either way, since #620 is about moving off this channel rather than onto it.

nic is not installable from any conda channel today, so the starter
workspaces cannot resolve the dependency they declare. The intended home
is prefix.dev's shared github-releases channel, which needs no recipe,
but onboarding onto it is an upstream pull request we do not control and
it has been open without movement.

Publish to our own channel in the meantime. Nothing is compiled here:
the recipe unpacks the archive GoReleaser already published and copies
the binary into the prefix, taking the sha256 from that release's
cosign-signed checksums.txt and failing when a checksum is absent, so a
conda user runs the bytes the release signed.

One Linux runner emits all six subdirs. Only the smoke test is
platform-bound, and --test native runs it for the build platform and
skips it elsewhere. Windows needs the zip archive and Library/bin.

Kept out of release.yml so it can backfill versions published before it
existed and re-run a failed upload without cutting a release. Uploads
via OIDC trusted publishing, so there is no long-lived token.
The pixi guidance claimed NIC was distributed via the shared
github-releases channel. It is not, and was not when the sentence was
written: onboarding there is an unmerged upstream change. Replace the
claim with the channel we actually publish to, how packages get there,
and what to check when a release does not show up.

Records the stable-only posture and why a prerelease tag cannot be
packaged unchanged, and keeps the github-releases channel documented as
the intended destination so the bridge has a stated end.
Review found four defects in the first cut.

The tag reached two run: blocks as a GitHub expression, which is
substituted into the script text before bash parses it, so a quote in
the value executed arbitrary commands in a job holding the publishing
identity. It now travels through env and is quoted, and it is validated
for shape before anything consumes it, because it still reaches gh, sed,
asset names and generated YAML.

A dispatch could name any ref, so the scripts this job runs came from
whatever branch was dispatched while the approver saw only a tag string.
Dispatches must now come from main, and the checkout is pinned to main:
the scripts and the release being repackaged are independent, and only
the latter comes from the tag.

The docs claimed only stable releases are published while the trigger
fired for prereleases. The tag is now checked against the release API
and a prerelease is refused, which also covers the dispatch path a
trigger filter would have missed.

The provenance claim was not backed by anything: checksums.txt was
fetched and trusted, though the release signs it. It is now
cosign-verified against the release workflow's identity before a digest
is read, so the claim describes what the code does.

Also: a build that emitted nothing reported a green publish, uploads
could not resume after a partial failure, and the job had no timeout or
concurrency group.
@viniciusdc
viniciusdc force-pushed the feat/publish-conda-channel branch from da11afc to 4ffd75c Compare August 26, 2026 14:15
Review found that a corrected recipe could never reach the channel. The
filename is name-version-<varianthash>_<number>, and the variant hash
covers the variant configuration rather than the recipe body: adding a
requirements block leaves the name identical, verified by building the
same source twice. Uploads skip filenames the channel already has, so
republishing a fixed recipe for a published version was skipped and
reported green, leaving the broken package served. The build number is
now a substituted slot with a dispatch input, and the comment claiming
reproducibility from the signed release is corrected - the bytes depend
on the recipe too, and the recipe is mutable.

The release trigger moves from published to released, so a release
candidate stops before the environment approval instead of after it. The
API check stays: it is the only thing covering the dispatch path.

gh's stderr is no longer discarded. A rate limit, a token-scope problem
or an outage was being reported as "no such release" in the step whose
documented recovery is to re-run this workflow.

Also records why provider tools stay in the starter workspaces rather
than the package: conda-forge publishes no opentofu for win-arm64, which
NIC does ship a package for, so an unconditional run dependency would
trade a working install for an unsolvable one.
@viniciusdc

Copy link
Copy Markdown
Collaborator Author

Hey @dcmcand, thanks for actually building the three-way filename test. All six are addressed in 4ffd75c, but I rebased onto main afterwards so your comments are anchored to da11afc5 and show as outdated. Mapping them so nothing looks skipped:

Build number. You're right, and I reproduced it before changing anything: adding a requirements.run block to the recipe gives back nebari-infrastructure-core-0.14.0-hb0f4dca_0.conda, byte-identical name. number: is now a __BUILD_NUMBER__ slot with a build_number dispatch input, defaulting to 0, and I confirmed _0 vs _1 come out distinct. I also fixed the comment you flagged. Saying the bytes are reproducible from the signed release was only half true when the recipe is mutable on main.

packaging.md:83. Rewritten. It now names the six subdirs conda-forge actually publishes and calls out win-arm64 as the exception, and it carries the reasoning for the open question rather than leaving it hanging.

types: [released]. Done. Better to stop an rc before the approval prompt than after it. The API check stays for the dispatch path.

2>/dev/null. Done, stderr is captured and echoed with the error. That one bothered me once you pointed at it, since the /releases outage is exactly the case it was mislabelling, in the step whose recovery is to re-run the workflow.

Attestation. Agreed on the gap, and I've left it out for now for the reason you gave: neither of us can test --generate-attestation until the trusted publisher is live, so it goes on the first real dispatch rather than in blind.

The two stale sentences. Split out as #622. pixi.toml.tmpl:8 is the one that bothers me more since it ships inside every generated starter, and it's wrong today regardless of how the channels list ends up.

On provider tools in the package: convinced, and I've recorded it in packaging.md as the decision rather than the question. The win-arm64 point settles it on its own, and scoping the constraint to the starter that needs it is a tighter pin anyway. Azure is the gap, and it can ride with whatever adds the Azure starter.

Still unverified is the -c nebari-dev/nebari channel spelling. The first dispatch is the first real test of that, and if it's wrong it's a one-line fix.

Re-requesting review since the branch has moved twice.

dcmcand
dcmcand previously approved these changes Aug 27, 2026

@dcmcand dcmcand left a comment

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.

Thanks Vinicius

@tylerpotts tylerpotts left a comment

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.

Built this end to end locally before reading the review threads: build-packages.sh v0.14.0 with rattler-build 0.74.0 and cosign 3.x emits all six subdirs, Verified OK on the control, one-byte-tampered checksums.txt rejected with invalid signature, and a pixi add from the local channel runs nic version reporting 0.14.0 / c1aa182b, so the ldflags survive. The nebari-dev/nebari spelling you flagged as unverified is fine on both counts: prefix.dev documents <namespace>/<channel> as the canonical reference for a non-primary channel and https://prefix.dev/<namespace>/<channel> as the pixi URL, and @nebari-dev/nebari already exists (empty).

One blocker, on the trigger, inline below. Two small non-gating notes:

  • docs/operations/packaging.md:110 and packaging/conda/README.md:31 say the workflow "runs on every published release"; it deliberately runs on released, so "every stable release" is the true sentence (the next paragraph in packaging.md already says so).
  • docs/operations/verifying-releases.md "Maintainer prerequisites" is where the one-time release environment setup lives; registering the prefix.dev trusted publisher (repo + publish-conda.yml + environment) belongs on that list, since packaging.md correctly notes nothing in-repo fails when it is missing.

Comment thread .github/workflows/publish-conda.yml Outdated
# would start a run and sit on the environment approval before failing the
# tag check. The API check below still has to stay - it is the only thing
# covering the dispatch path.
types: [released]

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.

With the way this repo actually cuts releases, this event fires about twenty minutes before the assets the build needs exist.

Releases here are created as published releases by a human, which pushes the tag and fires released immediately; release.yml then starts on the tag push and GoReleaser attaches checksums.txt and checksums.txt.sigstore.json at the end. From the API and run history:

v0.14.0  published_at=2026-08-25T13:48:10Z   release.yml created=13:48:13Z  finished=14:09:20Z
v0.13.0  published_at=2026-08-18T16:09:32Z   release.yml created=16:09:34Z  finished=16:32:06Z

GoReleaser's own fresh-release path would be fine (v2.18.0, the version the v0.14.0 run resolved, always creates a draft and undrafts after all uploads in internal/pipe/release/release.go doPublish). But createOrUpdateRelease in internal/client/github.go takes the other branch when the release already exists: it preserves the existing draft state and just updates it, so the release stays published through the whole upload window.

On that path this workflow starts, passes the isPrerelease == false check, and gh release download finds nothing. Worth knowing that gh release download --pattern A --pattern B exits 0 on a partial match (tested), so the failure surfaces at cosign verify-blob on a missing bundle rather than at the download. It does fail loudly and the dispatch recovery works, but the automatic path breaks for every release cut this way, and the release approval gate only hides it if the approver is slow.

Two small fixes, either works:

  • Trigger on workflow_run: workflows: [Release], types: [completed], guard on github.event.workflow_run.conclusion == 'success', take the tag from github.event.workflow_run.head_branch. The API check stays for the dispatch path.
  • Keep this trigger and add a bounded wait in the tag step: poll gh release view "$TAG" --json assets -q '.assets[].name' for checksums.txt.sigstore.json for a few minutes before proceeding.

Whichever lands should keep working if someone cuts a release by plain git push --tags, where GoReleaser's draft-then-publish ordering makes the current trigger correct.

fi

recipe="${workdir}/recipe-${subdir}.yaml"
sed -e "s|__VERSION__|${VERSION}|g" \

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.

Suggestion, not blocking: starters.yml asserts no __[A-Z_]*__ survives rendering before shipping; a one-liner here (grep -q '__[A-Z_]*__' "$recipe" && { echo "unsubstituted token in $recipe" >&2; exit 1; }) would give a new template slot without a matching sed -e a clear failure instead of a bad URL or a literal __BIN_DIR__ directory.

The release event fires about twenty minutes before the assets this
build reads exist. Releases here are usually created as published
releases by hand, which pushes the tag and publishes at once, and
Release then starts on the tag push and attaches checksums.txt and its
signature at the end: v0.14.0 published at 13:48:10 and Release finished
at 14:09:20; v0.13.0 published at 16:09:32 and finished at 16:32:06.

On that path the old trigger passed the prerelease check and then found
no assets to download, surfacing as a cosign failure on a missing bundle
rather than as a missing download, since gh release download exits 0 on
a partial pattern match.

Keying off a successful Release run is correct on that path and on git
push --tags, where GoReleaser creates a draft and undrafts only after
every upload. The tag now comes from the triggering run, and the release
API check stays: with no release-event type to filter on, it is the only
thing keeping release candidates off the channel.

Also adds the unsubstituted-token guard starters.yml already applies to
rendered starters, so a new template slot without a matching
substitution fails loudly instead of shipping a literal __TOKEN__ as a
URL or a directory name. It ignores comments, because the template's own
header explains the convention in prose.

Docs: the workflow no longer runs on every published release, and
registering the prefix.dev trusted publisher joins the one-time
maintainer prerequisites, since nothing in the repository fails when it
is missing.
@viniciusdc

Copy link
Copy Markdown
Collaborator Author

Hey @tylerpotts, the timing data settles it. I checked the API myself and it matches exactly: v0.14.0 published at 13:48:10 with Release finishing 14:09:20, v0.13.0 published 16:09:32 finishing 16:32:06. The release exists as published about three seconds before the workflow that uploads its assets even starts, so the trigger was firing into an empty release on the path this repo actually uses.

Went with workflow_run on Release rather than the polling wait, since it is correct on both paths instead of one, and a bounded poll would still be guessing at a window that is currently twenty minutes and not fixed. Tag comes from github.event.workflow_run.head_branch, guarded on conclusion == 'success'.

Worth flagging what that shifts: with no release-event type to filter on, the API check is now the only thing keeping release candidates off the channel, on both paths. It was already covering the dispatch path, so it is the same check doing more work rather than a new one, but it is load-bearing in a way it was not before.

Good catch on gh release download exiting 0 on a partial pattern match. That is the part that would have made this confusing to debug, since the failure lands on cosign complaining about a missing bundle rather than anywhere near the download.

Both non-gating notes taken. packaging.md and the packaging README no longer say "every published release", and registering the trusted publisher is now step 2 of the maintainer prerequisites in verifying-releases.md, with a note that nothing in-repo fails when it is missing.

Also took the token guard suggestion. It caught a false positive on its first run, which was worth having: the template's own header explains the __PLACEHOLDER__ convention in prose, so an unscoped grep fails every build. It now ignores comment lines. Verified both directions, clean template builds all six subdirs, a probe slot exits 1 naming the line.

Thanks for building it end to end before reading the threads, and for confirming the channel spelling. That was the last thing on the PR I could not check myself.

tylerpotts
tylerpotts previously approved these changes Aug 27, 2026

@tylerpotts tylerpotts left a comment

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.

Re-verified at beee1b7: clean full build via pixi exec (rattler-build 0.74.0, cosign 3.x) still gives Verified OK, six packages, native test passing, and the new token guard is silent on the real template. Mutation on the guard: appending probe: __PROBE__ to the template exits 1 right after the cosign step and names the line; the header comment's __PLACEHOLDER__ prose does not trip it. shellcheck and check-action-pins.sh clean.

On the trigger fix: workflows: ["Release"] matches release.yml's name, and workflow_run.head_branch is the tag on real tag-triggered Release runs (32855542098 event=push head_branch=v0.14.0), so the tag plumbing holds. The one thing nobody can test before merge is workflow_run itself, since GitHub only fires it once the file is on the default branch; the first release after merge is the first real run, same caveat as the upload.

Both doc notes and the guard suggestion are addressed. One non-gating suggestion inline: rc runs will now sit on the approval prompt before failing the API check, which is what released was chosen to avoid, and a one-clause pre-filter in the if removes that noise without changing where stable-only is enforced.

Approving.

Comment thread .github/workflows/publish-conda.yml Outdated
Co-authored-by: Tyler <49161327+tylerpotts@users.noreply.github.com>

@tylerpotts tylerpotts left a comment

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.

Thanks for all the work on this! The final suggestion was implemented so I'm green-lighting this. Feel free to merge when CI is passing

@viniciusdc
viniciusdc requested review from tylerpotts and removed request for tylerpotts August 27, 2026 19:52
@tylerpotts
tylerpotts self-requested a review August 27, 2026 19:56
@tylerpotts
tylerpotts requested a review from dcmcand August 27, 2026 19:56
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.

Distribute nic via a prefix.dev channel

3 participants