Skip to content

Enforce dev → releasepreview → 24h soak → release channel order - #1536

Open
IanButterworth wants to merge 2 commits into
mainfrom
ib/release-channel-ordering
Open

Enforce dev → releasepreview → 24h soak → release channel order#1536
IanButterworth wants to merge 2 commits into
mainfrom
ib/release-channel-ordering

Conversation

@IanButterworth

@IanButterworth IanButterworth commented Jun 12, 2026

Copy link
Copy Markdown
Member

Claude:


Previously all three channel deployments (dev / releasepreview / release) ran in parallel once packaging finished — the promotion order was enforced only by humans approving the GitHub environments in the right sequence. This PR makes the order dev → releasepreview → (≥24h soak) → release structural, and adds the end-to-end self-update tests to the test gate at the start of the release process.

Pipeline overview

flowchart TD
    tag(["push tag v*"]) --> build["build-juliaup<br/>(all targets)"]
    tag --> test["test-juliaup"]
    tag --> testsu["<b>test-selfupdate</b><br/>(new: end-to-end<br/>self-update tests)"]

    subgraph pkg ["packaging — env <b>package</b> (approval)"]
        direction LR
        punix["package-unix"]
        pwin["package-windows<br/>-msix / -msi"]
        pport["portable<br/>archives"]
    end
    build --> pkg
    test --> pkg
    testsu --> pkg

    ghrel["create-github-release<br/>(as prerelease)"]
    test --> ghrel
    testsu --> ghrel
    ghrel --> ghbin["deploy-github-release-binaries<br/>(prerelease assets)"]
    pport --> ghbin

    subgraph dev ["stage 1: dev channel — env <b>dev-channel</b> (approval)"]
        direction LR
        devws["winstore flight"]
        devs3["S3<br/>DEVCHANNELVERSION"]
    end
    ghrel --> dev
    punix --> dev
    pwin --> dev

    subgraph rp ["stage 2: releasepreview channel — env <b>release-preview-channel</b> (approval)"]
        direction LR
        rpws["winstore flight"]
        rps3["S3<br/>RELEASEPREVIEWCHANNELVERSION"]
    end
    dev --> rp

    gate{{"<b>release-gate</b> — env <b>release-gate</b><br/>wait timer: 24h (recommended setup)<br/>checks live channel: serves this version AND live ≥ 24h<br/>override: RELEASE_GATE_OVERRIDE variable (warns)"}}
    rp --> gate

    subgraph rel ["stage 3: release channel — env <b>release-channel</b> (approval)"]
        direction LR
        relws["winstore"]
        rels3["S3 + MSI"]
        relbrew["Homebrew"]
        relaur["AUR"]
        relgh["GitHub release<br/>(prerelease → release)"]
        relcrates["crates.io"]
    end
    gate --> rel
Loading

Ordering via needs

  • deploy-releasepreview-channel-{winstore,s3} now depend on both dev channel deploys.
  • All six deploy-release-channel-* jobs now depend on a new release-gate job, which depends on both releasepreview deploys.

The 24h soak gate

release-gate verifies against the live releasepreview channel — the RELEASEPREVIEWCHANNELVERSION file juliaup clients actually poll — that:

  1. the version being released is what releasepreview currently serves (ordering holds even across partial re-runs), and
  2. its Last-Modified is at least 24 hours old.

Because the check reads public state rather than workflow-internal timestamps, it cannot be bypassed by approving the release environment early, and it survives workflow re-runs.

Recommended one-time setup: create the release-gate environment (it is auto-created on first run) and give it a wait timer of 1440 minutes. The gate is then triggered when releasepreview finishes, sleeps 24h on GitHub's side, and passes on its own — fully hands-free. Without the wait timer the gate fails fast when run too early and must be re-run ("Re-run failed jobs") once the soak has elapsed; either way the 24h minimum is enforced.

Emergency override: set the repository variable RELEASE_GATE_OVERRIDE to the exact version being released and re-run the gate. It passes immediately with a prominent warning annotation and a note in the job summary. Scoping the override to one version means it cannot be left enabled by accident (a stale value is ignored with a notice).

Self-update tests in the release test gate

A test-selfupdate job (identical to the one in test.yml, running command_selfupdate_test with the selfupdate feature on Linux + macOS) now gates all packaging jobs and create-github-release alongside test-juliaup, so a release cannot proceed if the end-to-end self-update path is broken.

🤖 Generated with Claude Code

@davidanthoff

Copy link
Copy Markdown
Collaborator

This is great! A few thoughts:

  • I think 24 hours is not enough. I can't find the default self-update interval right now, but I think it might be 24 hours? I think something like a week is probably what we really need for the gate...
  • I am wondering whether we need to make the dev channel part of the sequence? My idea with that was really just that anyone who is actively working on Juliaup can use that to test something out, but I don't think we can expect any real quality control effort from that channel...

Comment thread .github/workflows/release.yml Outdated
Comment on lines +1139 to +1140
# For a hands-free flow, configure the `release-gate` environment with a
# wait timer of 1440 minutes: the job is then triggered when the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not aware what a "wait timer" is, but found it explained in https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments

Thing is, that means that the value 24 hours = 1440 minutes is now encoded in two places, this YAML file and that environment -- that may be unavoidable, and I don't object, but for the sake of future maintainers, who might, like me, not be familiar with this feature, how about including some more specific instructions. Something like this (I am sure someone else can word it better):

Suggested change
# For a hands-free flow, configure the `release-gate` environment with a
# wait timer of 1440 minutes: the job is then triggered when the
# For a hands-free flow, a repository admin should configure a GitHub
# environment `release-gate` under <https://github.com/JuliaLang/juliaup/settings/environments>,
# with a wait timer of 1440 minutes = 24 hours.
# This job is then triggered when the

Comment thread .github/workflows/release.yml Outdated
published=$(date -u -d "$last_modified" +%s)
now=$(date -u +%s)
age=$(( now - published ))
required=$(( 24 * 60 * 60 ))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am very slightly worried that someone will want to change this in the future (e.g. "one week" was already mentioned), and then out of the at least 10 places that hardcode 24 hours (9 places in this file, plus the release-gate GitHub environment) we will forget to update at least one.

How about adding a variable, say MINIMAL_REQUIRED_HOURS=24 or so, and then using that everywhere? Then we'd be down to 2 places. (Technically I guess the variable could be set in the environment config; but I am not sure that's better)

Anyway, this is overall of course a very minor concern. Feel free to ignore.

@fingolfin fingolfin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a good idea to me. Someone with the appropriate access rights must set up and then maintain the environment, obviously.

IanButterworth and others added 2 commits July 13, 2026 18:47
Previously all three channel deployments ran in parallel once
packaging finished, and the promotion order was enforced only by
humans approving the GitHub environments in the right sequence.

- The releasepreview deploy jobs now depend on both dev channel
  deploys, and all release channel jobs depend on a new `release-gate`
  job that runs after the releasepreview deploys.
- `release-gate` verifies against the live releasepreview channel
  (the RELEASEPREVIEWCHANNELVERSION file juliaup clients poll) that
  this version is actually published there and that it has been live
  for at least 24 hours. Because it reads public state, the check
  holds across re-runs and cannot be bypassed by early environment
  approval. Configuring the `release-gate` environment with a
  1440-minute wait timer makes the soak hands-free; without it the
  gate fails fast and is re-run after the soak time has elapsed.
- The gate can be overridden in an emergency by setting the
  repository variable RELEASE_GATE_OVERRIDE to the exact version
  being released and re-running the job; the gate then passes with a
  prominent warning. Scoping the override to a single version means
  it cannot be left enabled by accident.
- Add the end-to-end self-update tests (command_selfupdate_test, as
  run in test.yml) as a `test-selfupdate` job gating packaging and
  the GitHub release alongside `test-juliaup`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address review feedback: the 24h soak duration was hardcoded in ~9
places. Introduce a single workflow-level RELEASEPREVIEW_SOAK_HOURS
variable used by the release-gate script and referenced from comments,
so changing the soak period means editing one value here plus the wait
timer on the release-gate GitHub environment. Also spell out in the
job comment that a repository admin must create that environment under
the repo settings and set its wait timer to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@IanButterworth
IanButterworth force-pushed the ib/release-channel-ordering branch from 7ddbf08 to cf58867 Compare July 13, 2026 18:02
@IanButterworth

Copy link
Copy Markdown
Member Author

Feedback addressed.

I can't set up the required environment, so I'll hold off merging this for now so we can do a release while @davidanthoff is on vacation.

Claude:


Manual setup instructions

Nothing is needed before merge itself — the setup is all GitHub environment configuration, and it just needs to be in place before the next tagged release.

One-time setup (repo admin, before the next v* tag)

  1. Go to https://github.com/JuliaLang/juliaup/settings/environments and create an environment named release-gate (if it doesn't exist yet — GitHub also auto-creates it on the first run, but then without any protection rules).
  2. Set its wait timer to 1440 minutes (24 hours). This must match RELEASEPREVIEW_SOAK_HOURS: 24 defined at the top of release.yml — if the soak duration is ever changed, both places need updating.
  3. No reviewers/approvals needed on release-gate — human approval already exists on the dev-channel, release-preview-channel, and release-channel environments, which are unchanged.

Behavior with vs. without the wait timer

  • With it: fully hands-free — the gate triggers when the releasepreview deploys finish, GitHub sleeps 24h, then the check passes on its own.
  • Without it: the gate fails fast if run before the version has been live on releasepreview for 24h, and someone must click "Re-run failed jobs" after the soak elapses. The 24h minimum is enforced either way (it reads the live RELEASEPREVIEWCHANNELVERSION file's Last-Modified).

Per-release, only in emergencies

To skip the soak, set the repository variable RELEASE_GATE_OVERRIDE to the exact version being released (e.g. 1.20.7) and re-run the gate; it passes with a warning. Remove the variable afterwards (a stale value is ignored with a notice, so forgetting is harmless but noisy).

And a reminder: local commit cf58867 with the review-feedback changes still needs a rebase onto the updated remote branch and a push.

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.

3 participants