Skip to content

Release workflow hardening — lessons from 1.47.9/1.47.10 #14033

Description

@christian-byrne

Problem

The core 1.47.9 → 1.47.10 releases surfaced three concrete friction points in the release automation. Each cost manual recovery or produced a false signal. Fixes below are minimal and verified against the current workflow YAML on main.

Reference runs / PRs


1. publish-pypi tag-wait timeout takes down the whole release, with no auto-recovery

Where: release-biweekly-comfyui.yamlpublish-pypi job → step "Wait for release PR to be created and merged".

The step polls for tag v${TARGET_VERSION} for 60 × 30s = 30 min, then exit 1. That tag is only created by release-draft-create when a human merges the Release-labeled bump PR (#14029) — so the poll depends on a human action inside a hard 30-min window. Because create-comfyui-pr has needs: publish-pypi + if: … needs.publish-pypi.result == 'success', a timeout skips create-comfyui-pr too — a single run loses both the PyPI publish and the ComfyUI pin PR.

Observed (1.47.10): run 29979988134 failed exactly this way; recovery was a manual gh run rerun 29979988134 --failed after the tag existed (which reuses the resolved target_version — no re-resolve, no spurious next-patch bump).

Suggested fixes (any one; first is cleanest):

  • (Preferred) Decouple publish from the poll — make it release-triggered. Move the PyPI build+publish into release-draft-create.yaml, or a new workflow with on: release: { types: [published] } / on: push: tags: ['v*'], so PyPI publishes exactly when the tag/release is created — no polling, no window. The biweekly then only resolves + triggers the bump; the pin PR keys off the same release event.
  • (If keeping the poll) raise the timeout (a human merge realistically isn't 30 min), and on timeout end the job neutral rather than hard-fail, or gate create-comfyui-pr on the tag existing (not on publish-pypi.result == 'success') so a late publish doesn't also drop the pin PR.
  • At minimum: print "recover with gh run rerun <run-id> --failed once the tag exists" in the timeout branch and job summary.

2. comment_release_summary crash marks a fully-successful release as failure

Where: release-draft-create.yamlcomment_release_summary job → step "Post release summary comment" (./.github/actions/comment-release-links). Job is if: success(); the step has no continue-on-error.

Observed (1.47.10): run 29983428805build, draft_release, and publish_types all succeeded (tag cut, GH release created, npm types published), but the comment step crashed (Node error) and marked the whole run failure. A cosmetic step produced a false "release failed" signal.

Suggested fix (one line): add continue-on-error: true to the "Post release summary comment" step (or the job). Optionally keep a ::warning:: so the crash stays visible without failing the run. A comment must never gate release success.


3. No automated "release-done" assertion — the 1.47.9 stranded-commits case went undetected

Where: nothing in the four release workflows asserts the release actually reached users after publish.

Observed (1.47.9): v1.47.9 published to PyPI while the QA fix set (19 commits) sat unreleased past the tag — desktop/self-host users got a version advertised as fixed that wasn't, forcing the 1.47.10 re-cut. Only a manual git rev-list caught it.

Suggested fix: add a release-done assertion job (after create-comfyui-pr, or a small workflow on release publish) that hard-fails + pings if any of:

  • git rev-list v${TARGET}..origin/${TARGET_BRANCH} --count != 0 (commits stranded past the tag)
  • PyPI .info.version of comfyui-frontend-package != ${TARGET}
  • ComfyUI master:requirements.txt pin != comfyui-frontend-package==${TARGET} (warn-only if the pin PR is intentionally left for a human to merge)

4. Smaller observations

  • publish-pypi's "Wait" step also blocks a manual dispatch (needs_release == true) on a human merge inside the same run — same coupling as Bump braces from 3.0.2 to 3.0.3 in /tests-ui #1; decoupling fixes it too.
  • The biweekly job summary should print the resolved target_version/target_branch and the recovery command up front, so a failed run is self-documenting.
  • make_latest semantics are correct (core core/* base → non-latest, so a core patch can't steal latest from e.g. v1.48.x) — worth a one-line inline comment in release-draft-create.yaml to prevent a future "why isn't the core release latest?" edit.

Priority

  1. Configure vite to copy from src to dist #2 — one-line continue-on-error; kills false release-failure alarms. Do now.
  2. Bump braces from 3.0.2 to 3.0.3 in /tests-ui #1 — decouple publish from the tag-wait poll (or make it tolerant + recoverable). Removes the failure class that lost work in 1.47.10.
  3. Migrate domWidget from js to ts #3release-done assertion job; auto-catches the 1.47.9 stranded-commits class.
  4. Migrate widgets.js #4 — polish / comments.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions