Skip to content

feat: add opt-in freeze-on-ready to release-ready.yml#18

Open
VascoSch92 wants to merge 6 commits into
mainfrom
vasco/freeze-on-ready
Open

feat: add opt-in freeze-on-ready to release-ready.yml#18
VascoSch92 wants to merge 6 commits into
mainfrom
vasco/freeze-on-ready

Conversation

@VascoSch92

Copy link
Copy Markdown
Member

Why

Consumers that keep release PRs as drafts (draft-pull-request: true) use "mark
ready for review" as the release-cut signal. But on the primary branch that
signal can't freeze the release: release-please rebases its PR onto the base
branch on every run, and merging the PR tags the base branch's tip — so anything
merged after "ready" still ships. The only true freeze is cutting the release
onto a release/** branch (which release.yml already runs on). This makes that
automatic and opt-in.

Requested for the SDK adoption in OpenHands/software-agent-sdk#3939.

What

A new opt-in job in release-ready.yml, mirroring the existing
release-test-dispatch-event / release-test-workflows pattern (separate job,
input-gated, own permissions). Default off, so existing consumers are untouched.

New inputs:

  • freeze-on-ready (boolean, default false)
  • freeze-branch-prefix (string, default release/)

When freeze-on-ready: true, marking a release PR ready for review:

  1. cuts release/<major.minor> at the PR's base SHA using the App token (a
    GITHUB_TOKEN ref wouldn't emit a push event, so release-please would never
    run on it), and
  2. returns the chore(main) PR to draft so it can't be merged from the moving
    base branch — it stays the next-release preview.

release-please then opens the release PR on the frozen branch. Marking that
one ready runs the normal label/Slack/test path (the freeze job skips itself
because its base already starts with freeze-branch-prefix).

Notes for reviewers

  • Design choice: the frozen chore(main) PR is returned to draft (prevents
    an accidental merge from the moving base — same convertPullRequestToDraft
    used in OpenHands/OpenHands's pr-readiness-confirm.yml). The alternative is to
    just comment and leave it (today's "leave the shadow alone" guidance). Happy to
    switch to comment-only or make it a third input if you prefer.
  • Permissions: the freeze job needs contents: write + pull-requests: write
    • issues: write; consumers grant these only when they opt in.
  • Idempotent: if the freeze branch already exists it's left as-is.
  • Testing: YAML validated and the job gating verified by hand, but the runtime
    path (real release PR + ready_for_review) is not yet dogfooded — this repo's
    own pr-ready.yml still has freeze-on-ready off. Suggest enabling it here (or
    an integration test) as a follow-up before a consumer relies on it.

@github-actions github-actions Bot added the type: feat A new feature label Jul 1, 2026
@VascoSch92
VascoSch92 requested review from enyst and jlav July 1, 2026 15:46
Previously the release-ready job labeled + Slacked + set should_trigger_tests on
the chore(main) PR before the freeze job drafted it, so the shadow PR was
labeled (firing the test suites on it), announced as 'ready for review', and
then converted to draft. Add a should_freeze path: when freeze-on-ready is on
and the PR is on the primary branch, defer all of that to the frozen branch's
release PR and only run the freeze.
@enyst

enyst commented Jul 1, 2026

Copy link
Copy Markdown
Member

@OpenHands We want to change from a workflow in agent-sdk repo, based on its own custom prepare-release skill, to a skill and human/agent workflow based on this repo’s release-please based action. Understand these. Find also the related SDK PR, that introduces release-please. Understand the prepare-release skill before and after the change introduced by BOTH these PRs.

Human workflow:
Before:

  • ask the agent to run prepare-release skill
  • agent created a release PR, applied three testing labels, tests run; that’s it (verify all this)
  • human can see if tests fail, then they fix and re-apply labels, or tests succeed, and human merges.

After:
?

Agent workflow:
Before:

  • (tell me here what is the agent doing according to the Before prepare-release skill)

After:

  • (similar)

Answer us directly here on github.

@openhands-ai

openhands-ai Bot commented Jul 1, 2026

Copy link
Copy Markdown

I'm on it! enyst can track my progress at all-hands.dev

enyst commented Jul 1, 2026

Copy link
Copy Markdown
Member

@enyst I traced both pieces:

Human workflow

Before

Verified. The current SDK release skill tells the agent/human to pick an explicit SemVer version and trigger .github/workflows/prepare-release.yml.

That workflow:

  1. validates the X.Y.Z input;
  2. creates rel-X.Y.Z;
  3. runs make set-package-version version=X.Y.Z;
  4. commits/pushes the version changes;
  5. opens Release vX.Y.Z against main;
  6. applies exactly the three test-trigger labels: integration-test, behavior-test, test-examples;
  7. notifies #proj-agent if Slack is configured.

I also checked recent SDK release PRs such as OpenHands/software-agent-sdk#3931 / #3888 / #3863: they have those three labels, and the corresponding labeled workflow runs exist for integration / behavior / examples.

Then the human watches the release PR. If tests fail, they fix on the rel-* branch and rerun/reapply labels as needed. If tests pass, the human merges. On merge, the old create-release.yml sees a merged rel-* PR, creates vX.Y.Z, creates the GitHub Release, and dispatches the PyPI / server image / release binaries follow-up workflows.

After, with SDK PR #3939 plus this freeze-on-ready PR enabled

No one asks the agent to create a release branch/PR anymore. Release-please keeps a draft release PR open automatically after pushes to main, e.g. chore(main): release X.Y.Z. The version is inferred from Conventional Commit PR titles, not supplied manually.

The human workflow becomes:

  1. Review the draft chore(main): release X.Y.Z PR that release-please maintains: version bump, release notes/checklist, deprecation deadlines, release-note-required content.
  2. When ready to cut/freeze, mark that chore(main) PR Ready for review.
  3. With this PR’s freeze-on-ready: true, that ready action does not run release tests on the moving main PR. Instead it:
    • creates release/X.Y at the release PR’s base SHA using the App token, and
    • converts the chore(main) PR back to draft so it cannot accidentally be merged from moving main.
  4. The branch push causes release-please to open a second, frozen PR: chore(release/X.Y): release X.Y.Z.
  5. Human marks the frozen chore(release/X.Y) PR Ready for review. Because its base already starts with release/, the freeze job skips itself and the normal release-ready path runs: release: ready + behavior-test + integration-test + test-examples are applied, Slack is notified, and the tests run.
  6. If tests fail, fix normally on main and cherry-pick/backport the fix to release/X.Y (or otherwise update the release branch), then let release-please update the frozen release PR and rerun/reapply labels as needed.
  7. If tests pass, human merges the frozen release PR. release-please tags/publishes vX.Y.Z; pypi-release.yml, release-binaries.yml, and downstream version bump automation continue from the release: published event.
  8. After publishing, merge release/X.Y back into main so main learns the released version/manifest and the shadow chore(main) PR is cleared/advanced.

If SDK PR #3939 is merged without this PR’s freeze option enabled, the “After” is weaker: marking the chore(main) draft ready just labels/tests that same moving-base PR, and anything merged to main before final merge can still ship. That is exactly the gap this release-actions PR is addressing.

Concretely, SDK PR #3939 would need to update its caller from @v0.2.0 to a ref containing this change, grant contents: write, and pass freeze-on-ready: true (plus keep issues: write / pull-requests: write) to get the frozen workflow above.

Agent workflow

Before, per the current SDK release skill

The agent is doing release orchestration around the custom workflow:

  1. determine/confirm the target SemVer version with the human;
  2. trigger prepare-release.yml via UI/API, or tell the human exactly where to run it;
  3. wait for/locate the generated rel-X.Y.Z PR;
  4. inspect the version changes and PR checklist;
  5. handle deprecation-deadline failures if CI reports them;
  6. monitor the three labeled suites: integration, behavior, examples;
  7. optionally trigger an evaluation run;
  8. summarize status and explicitly ask for human approval before merge;
  9. only after explicit approval, merge the release PR;
  10. verify PyPI publication/downstream version bump PRs and draft the release announcement.

After, the skill should change to release-please orchestration

The agent should no longer dispatch prepare-release.yml, create rel-*, or choose a version manually. It should:

  1. find the current release-please draft PR (chore(main): release X.Y.Z) and explain what release-please selected from merged Conventional Commit titles;
  2. review generated version bumps, checklist, release notes, deprecation deadlines, and release-note-required entries;
  3. ask the human to mark the chore(main) PR Ready for review as the freeze/cut signal, or do it only if explicitly instructed;
  4. after freeze-on-ready runs, locate the frozen chore(release/X.Y): release X.Y.Z PR;
  5. ensure the original chore(main) PR is left alone/as draft — it is now the next-release preview, not the PR to merge;
  6. ask the human to mark the frozen PR Ready, or do it only if explicitly instructed, so the release labels/tests start;
  7. monitor integration-test, behavior-test, and test-examples, diagnose failures, and guide fixes/backports onto release/X.Y;
  8. optionally run evaluation;
  9. summarize status and get explicit human approval before merging the frozen release PR;
  10. after merge, verify the GitHub Release/PyPI/binaries/downstream bump PRs, then guide the merge-back from release/X.Y to main and draft the announcement.

So the biggest requested follow-up is not just workflow config: the SDK .agents/skills/sdk-release/SKILL.md must be rewritten, because in #3939 it still describes a workflow that the same PR deletes.

This comment was created by an AI agent (OpenHands) on behalf of the user.

@openhands-ai

This comment was marked as duplicate.

@enyst

enyst commented Jul 1, 2026

Copy link
Copy Markdown
Member

Notes as I’m reading the agent’s answer:

HUMAN WORKFLOW

Human marks the frozen chore(release/X.Y) PR Ready for review.

I think maybe we can open that as ready for review, or mark it automatically ready for review, once opened? So that the human doesn’t have to do it twice.

If tests fail, fix normally on main and cherry-pick/backport the fix to release/X.Y (or otherwise update the release branch), then let release-please update the frozen release PR
release/X.Y
release/X.Y.Z

I’m a bit confused, why is the agent sometimes saying “X.Y” and other times “X.Y.Z”, will the human know which is which? Will we make multiple “X.Y” branches in time, for the same X and Y ?

Does the agent know well enough the procedure, so that the human can say “once we merge the PR about abcd, backport it to the release branch”, and it will understand which release branch?

@enyst

enyst commented Jul 1, 2026

Copy link
Copy Markdown
Member

AGENT WORKFLOW

  1. ask the human to mark the chore(main) PR Ready for review as the freeze/cut signal, or do it only if explicitly instructed;

I think the flow could be:

  • human starts this agent skill by telling the agent to prepare or make/cut a release.
  • if so, it’s the agent who should mark this for ready for review, and there’s no need to wait for confirmation, right?

ensure the original chore(main) PR is left alone/as draft — it is now the next-release preview, not the PR to merge;

Should we switch it back to draft automatically, once the new branch/PR exists or was dispatched?

ask the human to mark the frozen PR Ready, or do it only if explicitly instructed, so the release labels/tests start;

This is the second PR, the one we want; could we mark it ready automatically, there is no need to wait, right?

then guide the merge-back from release/X.Y to main and draft the announcement.

Why “guide”, should maybe the agent get some … command, in the skill, to do this merge back?
Draft the announcement is confusing me, doesn’t release-please make release notes?

@enyst

enyst commented Jul 1, 2026

Copy link
Copy Markdown
Member

So the biggest requested follow-up is not just workflow config: the SDK .agents/skills/sdk-release/SKILL.md must be rewritten, because in #3939 it still describes a workflow that the same PR deletes.

This may be worth looking into, also…?

@VascoSch92 I left above some notes on the steps. I would really love it if we can give the human a recipe or two recipes, and the agent is doing the rest… Maybe as commands? /release and /release-merge

Tiny additional note: if I understand correctly, a PR title has “frozen” in it; maybe it’s worth considering to call it “current” if we can. Sorry to be a nitpicker! I’m trying to guess, looking from a human who doesn’t know the process in detail, what is the easiest and less error-prone wording we can give them?

@jlav

jlav commented Jul 1, 2026

Copy link
Copy Markdown
Member

Correct me if I'm wrong, but aren't the releases in software-agent-sdk already cut directly from main? I understand it currently has release preparation branches, but ultimately that gets merged to main and then we publish artifacts based on a commit sha that lives on main. So we effectively pick up any new changes merged to main.

Is that the issue we're trying to solve? Or is this new functionality being introduced because the tests we invoke are time consuming and we want to minimize churn?

I'm asking because I'd like to better understand what we're trying to accomplish before adding additional machinery that makes the software-agent-sdk release actions look so much different than the ones applied to other repos.

@enyst

enyst commented Jul 1, 2026

Copy link
Copy Markdown
Member

Correct me if I'm wrong, but aren't the releases in software-agent-sdk already cut directly from main? I understand it currently has release preparation branches, but ultimately that gets merged to main and then we publish artifacts based on a commit sha that lives on main.

Yes

So we effectively pick up any new changes merged to main.

No… During the life of the release PR, we run three workflows with tests (and we may want to add a security one). If tests fail, or deprecation deadlines, or compatibility checks : fix PR -> main -> cherry picked on release preparation branch.

Sometimes, we re-run all tests; sometimes this way we don’t need to re-run tests; often, we may want to re-run some and not others. Yes they do take some time. And live LLM calls - all of them are live.

If, on the other hand, in this time release-please keeps adding random other commits from main, well, then we’d always have to re-run all tests. That brings some question about costs, timing (hey please stop merging?) and for small releases, about stability e.g. maybe all we need was a small patch.

@jlav jlav 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.

Thanks for the added context. I've got some additional questions, but the overall approach makes sense to me.

after merge, verify the GitHub Release/PyPI/binaries/downstream bump PRs, then guide the merge-back from release/X.Y to main and draft the announcement.

Right now, most repos are set to only allow squash merging PRs to main. If you squash merge the release-branch into, I don't think release-please will correctly diff the history between what's currently on main vs the last release tag, which was pointed at a commit that is only reachable from the release branch.

If tests fail, fix normally on main and cherry-pick/backport

Right now, the release actions are using github-style release notes, and github style release note derive the PR categorization from a label applied to each PR. I'd suggest
updating these instructions to say that a PR should be used to backport the fix, since I think most folks will interpret this to mean to run an actual git cherry-pick directly on the release branch, which won't show up in the release notes.

Given those issues I flagged above, I would suggest creating a minimal repo where you import this action from vasco/freeze-on-ready and actually try running through some release flows to make sure that it works as you expect and that the release notes it generates are actually accurate. When I first implemented these actions, I had an agent generate a bunch of fake PRs against a test repo and actually try creating a new release from main and doing patch releases between minor versions.

Comment thread README.md
from `main` and pull in everything that landed after your cut. You publish from the branch
instead, then merge back.

### Automating the freeze on ready-for-review

@jlav jlav Jul 13, 2026

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.

It would be helpful to add step by step instructions to the readme that explain how to cut a release.

This section mostly includes technical details, so it's hard to understand how to actually use it in practice.

Called-workflow job permissions are validated statically at workflow
startup, before any job runs and regardless of `if:` gating. The
release-test-workflows job declares `actions: write` and the
release-test-dispatch-event job declares `contents: write`, so the
documented `read` grants killed every run as startup_failure — no jobs,
no logs, only "This run likely failed because of a workflow file issue".

Reproduced against @main with no freeze inputs at all, so this predates
freeze-on-ready; it went unnoticed because release-ready.yml had never
actually run in any repo.
The freeze job already tracked `created`, but never used it: when the
freeze branch existed it skipped the createRef, then posted the same
"Release frozen" comment and returned the PR to draft anyway.

That reads as "I marked the PR ready and it silently un-readied itself,
where is my release?" — which is exactly how it was hit. The PR is still
returned to draft (merging from the moving base branch is still wrong),
but the comment now says nothing was frozen, points at the branch that
owns the release, and names the merge-back as the way out of the loop.
release-please reuses ONE release PR per branch and retitles it as the
proposed version changes. Every freeze therefore left another comment on
the same PR, each still saying "merge that one to publish" in the present
tense about a release that had already shipped — so a PR titled
"release 0.4.0" carried a comment pointing at a long-merged 0.3.1 cut.

Tag each freeze comment with a hidden marker and delete earlier marked
ones before posting, so exactly one — the current one — is ever present.
Only comments carrying the marker AND authored by a bot are touched.
Cleanup is best-effort: the branch is already cut by then, so failing to
tidy up must not fail the freeze.

Also name the version in the text; the branch alone (release/0.3) doesn't
say which cut it described.
The marker only helps comments written from now on. Comments already
sitting on a consumer's release PR — from any earlier ref of this
workflow — were invisible to the cleanup and would stay there forever,
which is the whole problem the marker was meant to solve.

Match those by their distinctive heading as well, and only when the
author is a Bot, so a human quoting a freeze comment is never deleted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants