Skip to content

feat(cicd): fail when fixes sit unshipped on a consumed release line - #14429

Merged
christian-byrne merged 8 commits into
mainfrom
cb__release-stranded-guard
Aug 9, 2026
Merged

feat(cicd): fail when fixes sit unshipped on a consumed release line#14429
christian-byrne merged 8 commits into
mainfrom
cb__release-stranded-guard

Conversation

@christian-byrne

Copy link
Copy Markdown
Contributor

Implements the release-done assertion from #14033 §3.

Why

v1.47.10 was tagged 19 hours before the templates dropdown fix was backported
onto core/1.47. The backport merged, every tool reported it done, ComfyUI
0.29.0 pinned the older tag, and the fix reached nobody for a week while five
users reported the bug it had already fixed (#14063, #14131, #14212, #14351,
#14397).

Merged onto a release branch is not shipped, and nothing asserted the difference.
This is the second occurrence — v1.47.9 stranded 19 QA commits past its tag and
forced the 1.47.10 re-cut. The lesson was written into the core-release runbook
and recurred one release later, which is the argument for a machine check.

What it does

Daily, on release publish, and on demand: for each release line users actually
consume, diff the branch against its newest tag. Fix commits sitting past the tag
fail the job and post to #frontend-releases. Also asserts the published PyPI
version matches the branch's tag.

Scoped deliberately. Only the minor ComfyUI pins and the newest minor on PyPI
are checked. A sweep of every release branch found e.g. core/1.48 with 45
stranded commits and no 1.48 on PyPI at all — dead and unreleased lines have
stranded commits by design, and alerting on them would be pure noise.

Verification

Run against live state before 1.47.11 was cut, it exited 1 and named all four
stranded core/1.47 fixes:

[FAIL] 4 fix commit(s) sit past v1.47.10 on core/1.47 and have not shipped:
    34e57c671d [backport core/1.47] fix: preserve image pixels under the mask ... (#14191)
    c67b2c7f70 [backport core/1.47] fix: sync run warning icon ... (#14188)
    a2b2683d29 [backport core/1.47] Fix migration of legacy reordered ... (#14116)
    9a65619638 [backport core/1.47] fix(select): lift Reka dropdowns above modal stack (#14065)

Built test-first: the red commit ships a resolving stub with all 11 specs failing,
the green commit implements them. The fix-detection pattern initially missed
#14116 ("Fix migration of ..." — no conventional-commit colon), which was added as
a failing case before widening the pattern.

Needs before it can notify

Set the SLACK_FRONTEND_RELEASES_CHANNEL_ID repo variable. Without it the job
still fails correctly and warns that the notification was skipped.

Refs #14033

@christian-byrne
christian-byrne requested a review from a team July 30, 2026 23:08
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jul 30, 2026
@dosubot

dosubot Bot commented Jul 30, 2026

Copy link
Copy Markdown

📄 Knowledge review

Dosu skipped reviewing this PR because your organization has used its 200 included credits for the month. Your usage will reset on 2026-08-01. To have Dosu review this PR before then, ask your organization admin to upgrade to a pro account.


Leave Feedback Ask Dosu about ComfyUI_frontend Add Dosu to your team

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

🎭 Playwright: ✅ 1790 passed, 0 failed · 2 flaky

📊 Browser Reports
  • chromium: View Report (✅ 1769 / ❌ 0 / ⚠️ 2 / ⏭️ 5)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 18 / ❌ 0 / ⚠️ 0 / ⏭️ 0)

📦 Bundle: 8.69 MB gzip 🟢 -327 B

Details

Summary

  • Raw size: 36.6 MB baseline 36.6 MB — 🟢 -610 B
  • Gzip: 8.69 MB baseline 8.69 MB — 🟢 -327 B
  • Brotli: 6.06 MB baseline 6.06 MB — 🟢 -386 B
  • Bundles: 437 current • 437 baseline • 4 added / 4 removed

Category Glance
Utilities & Hooks 🟢 -532 B (549 kB) · Graph Workspace 🟢 -78 B (1.36 MB) · Vendor & Third-Party ⚪ 0 B (16.3 MB) · Other ⚪ 0 B (14 MB) · Data & Services ⚪ 0 B (3.5 MB) · Panels & Settings ⚪ 0 B (570 kB) · + 5 more

App Entry Points — 3.67 kB (baseline 3.67 kB) • ⚪ 0 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-C7v9Upev.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.84 kB 🟢 -1.61 kB
assets/index-CadTkgic.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.84 kB 🔴 +1.59 kB

Status: 1 added / 1 removed

Graph Workspace — 1.36 MB (baseline 1.36 MB) • 🟢 -78 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-CIaps-j6.js (removed) 1.35 MB 🟢 -1.35 MB 🟢 -294 kB 🟢 -221 kB
assets/GraphView-BhrbbuB4.js (new) 1.35 MB 🔴 +1.35 MB 🔴 +294 kB 🔴 +221 kB

Status: 1 added / 1 removed / 2 unchanged

Views & Navigation — 124 kB (baseline 124 kB) • ⚪ 0 B

Top-level views, pages, and routed surfaces

Status: 17 unchanged

Panels & Settings — 570 kB (baseline 570 kB) • ⚪ 0 B

Configuration panels, inspectors, and settings screens

Status: 26 unchanged

User & Accounts — 27 kB (baseline 27 kB) • ⚪ 0 B

Authentication, profile, and account management bundles

Status: 10 unchanged

Editors & Dialogs — 125 kB (baseline 125 kB) • ⚪ 0 B

Modals, dialogs, drawers, and in-app editors

Status: 8 unchanged

UI Components — 67.1 kB (baseline 67.1 kB) • ⚪ 0 B

Reusable component library chunks

Status: 14 unchanged

Data & Services — 3.5 MB (baseline 3.5 MB) • ⚪ 0 B

Stores, services, APIs, and repositories

Status: 17 unchanged

Utilities & Hooks — 549 kB (baseline 550 kB) • 🟢 -532 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useConflictDetection-JQLR5gZb.js (removed) 236 kB 🟢 -236 kB 🟢 -52.9 kB 🟢 -43.1 kB
assets/useConflictDetection-D5HqmLAN.js (new) 235 kB 🔴 +235 kB 🔴 +52.5 kB 🔴 +42.8 kB

Status: 1 added / 1 removed / 37 unchanged

Vendor & Third-Party — 16.3 MB (baseline 16.3 MB) • ⚪ 0 B

External libraries and shared vendor chunks

Status: 17 unchanged

Other — 14 MB (baseline 14 MB) • ⚪ 0 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/main-8sTibyJJ.js (new) 45.1 kB 🔴 +45.1 kB 🔴 +13.1 kB 🔴 +11.4 kB
assets/main-CZxGHhq4.js (removed) 45.1 kB 🟢 -45.1 kB 🟢 -13.1 kB 🟢 -11.4 kB

Status: 1 added / 1 removed / 285 unchanged

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 67.1 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 55.2 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.8 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 50.7 MB heap
large-graph-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 63.0 MB heap
large-graph-pan: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 62.3 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 57.4 MB heap
minimap-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.1 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 51.5 MB heap
subgraph-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 67.2 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 51.3 MB heap
subgraph-transition-enter: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 140ms TBT · 96.7 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 67.3 MB heap
vue-large-graph-idle: · 56.2 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 158.9 MB heap
vue-large-graph-pan: · 56.2 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 141ms TBT · 160.0 MB heap
workflow-execution: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.3 MB heap

⚠️ 8 regressions detected

Show regressions
Metric Baseline PR (median) Δ Sig
canvas-idle: task duration 406ms 495ms +22% ⚠️ z=3.2
canvas-mouse-sweep: layout duration 3ms 4ms +61% ⚠️ z=2.4
canvas-zoom-sweep: task duration 329ms 399ms +21% ⚠️ z=3.1
large-graph-idle: task duration 573ms 739ms +29% ⚠️ z=3.6
large-graph-pan: task duration 982ms 1341ms +37% ⚠️ z=6.0
large-graph-pan: script duration 304ms 449ms +48% ⚠️ z=2.0
minimap-idle: task duration 569ms 686ms +20% ⚠️ z=3.3
subgraph-idle: task duration 401ms 460ms +15% ⚠️ z=2.9
All metrics
Metric Baseline PR (median) Δ Sig
canvas-idle: avg frame time 17ms 17ms -0% z=-0.5
canvas-idle: p95 frame time 17ms 17ms +0%
canvas-idle: layout duration 0ms 0ms +0%
canvas-idle: style recalc duration 7ms 8ms +26% z=-2.8
canvas-idle: layout count 0 0 +0%
canvas-idle: style recalc count 11 10 -14% z=-2.9
canvas-idle: task duration 406ms 495ms +22% ⚠️ z=3.2
canvas-idle: script duration 14ms 18ms +33% z=-3.2
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: heap used 66.9 MB 67.1 MB +0%
canvas-idle: DOM nodes 22 19 -14% z=-2.8
canvas-idle: event listeners 6 4 -33% z=-1.6
canvas-mouse-sweep: avg frame time 17ms 17ms -0% z=-0.9
canvas-mouse-sweep: p95 frame time 17ms 17ms +1%
canvas-mouse-sweep: layout duration 3ms 4ms +61% ⚠️ z=2.4
canvas-mouse-sweep: style recalc duration 26ms 39ms +52% z=-1.2
canvas-mouse-sweep: layout count 12 12 +0%
canvas-mouse-sweep: style recalc count 72 76 +6% z=-1.1
canvas-mouse-sweep: task duration 657ms 918ms +40% z=0.9
canvas-mouse-sweep: script duration 84ms 126ms +51% z=-1.5
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: heap used 61.4 MB 55.2 MB -10%
canvas-mouse-sweep: DOM nodes 54 -111 -305% z=-66.8
canvas-mouse-sweep: event listeners 4 -74 -1938% z=-19.7
canvas-zoom-sweep: avg frame time 17ms 17ms +0% z=-0.3
canvas-zoom-sweep: p95 frame time 17ms 17ms +0%
canvas-zoom-sweep: layout duration 0ms 1ms +25% z=-0.6
canvas-zoom-sweep: style recalc duration 13ms 18ms +41% z=-0.5
canvas-zoom-sweep: layout count 6 6 +0%
canvas-zoom-sweep: style recalc count 32 32 -2% z=0.5
canvas-zoom-sweep: task duration 329ms 399ms +21% ⚠️ z=3.1
canvas-zoom-sweep: script duration 16ms 21ms +36% z=-1.9
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: heap used 69.7 MB 69.8 MB +0%
canvas-zoom-sweep: DOM nodes 79 77 -3% z=-3.5
canvas-zoom-sweep: event listeners 19 19 +0% z=-0.9
dom-widget-clipping: avg frame time 17ms 17ms +0% z=0.1
dom-widget-clipping: p95 frame time 17ms 17ms -1%
dom-widget-clipping: layout duration 0ms 0ms +0%
dom-widget-clipping: style recalc duration 6ms 8ms +41% z=-2.1
dom-widget-clipping: layout count 0 0 +0%
dom-widget-clipping: style recalc count 13 12 -12% z=-3.2
dom-widget-clipping: task duration 293ms 377ms +28% z=0.7
dom-widget-clipping: script duration 40ms 60ms +49% z=-2.4
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: heap used 50.3 MB 50.7 MB +1%
dom-widget-clipping: DOM nodes 22 19 -14% z=-2.2
dom-widget-clipping: event listeners 2 1 -50% variance too high
large-graph-idle: avg frame time 17ms 17ms -0% z=-1.0
large-graph-idle: p95 frame time 17ms 17ms +0%
large-graph-idle: layout duration 0ms 0ms +0%
large-graph-idle: style recalc duration 6ms 8ms +35% z=-4.0
large-graph-idle: layout count 0 0 +0%
large-graph-idle: style recalc count 10 9 -10% z=-8.3
large-graph-idle: task duration 573ms 739ms +29% ⚠️ z=3.6
large-graph-idle: script duration 73ms 113ms +55% z=1.0
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: heap used 63.8 MB 63.0 MB -1%
large-graph-idle: DOM nodes -281 -282 +0% z=-339.3
large-graph-idle: event listeners -149 -149 +0% z=-28.7
large-graph-pan: avg frame time 17ms 17ms +0% z=0.3
large-graph-pan: p95 frame time 17ms 17ms +0%
large-graph-pan: layout duration 0ms 0ms +0%
large-graph-pan: style recalc duration 10ms 14ms +42% z=-3.6
large-graph-pan: layout count 0 0 +0%
large-graph-pan: style recalc count 69 69 -1% z=-1.7
large-graph-pan: task duration 982ms 1341ms +37% ⚠️ z=6.0
large-graph-pan: script duration 304ms 449ms +48% ⚠️ z=2.0
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: heap used 58.6 MB 62.3 MB +6%
large-graph-pan: DOM nodes -282 -285 +1% z=-184.0
large-graph-pan: event listeners -149 -150 +1% z=-187.1
large-graph-zoom: avg frame time 17ms 17ms +0%
large-graph-zoom: p95 frame time 17ms 17ms +0%
large-graph-zoom: layout duration 6ms 7ms +21%
large-graph-zoom: style recalc duration 12ms 17ms +41%
large-graph-zoom: layout count 60 60 +0%
large-graph-zoom: style recalc count 66 66 -1%
large-graph-zoom: task duration 1208ms 1514ms +25%
large-graph-zoom: script duration 382ms 530ms +39%
large-graph-zoom: TBT 0ms 0ms +0%
large-graph-zoom: heap used 54.2 MB 57.4 MB +6%
large-graph-zoom: DOM nodes -282 -287 +2%
large-graph-zoom: event listeners -151 -153 +1%
minimap-idle: avg frame time 17ms 17ms -0% z=-0.4
minimap-idle: p95 frame time 17ms 17ms +1%
minimap-idle: layout duration 0ms 0ms +0%
minimap-idle: style recalc duration 6ms 8ms +34% z=-2.5
minimap-idle: layout count 0 0 +0%
minimap-idle: style recalc count 9 8 -17% z=-3.0
minimap-idle: task duration 569ms 686ms +20% ⚠️ z=3.3
minimap-idle: script duration 69ms 109ms +58% z=1.1
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: heap used 65.0 MB 65.1 MB +0%
minimap-idle: DOM nodes -282 -286 +1% z=-222.7
minimap-idle: event listeners -149 -148 -1% z=-231.1
subgraph-dom-widget-clipping: avg frame time 17ms 17ms -0% z=0.1
subgraph-dom-widget-clipping: p95 frame time 17ms 17ms -1%
subgraph-dom-widget-clipping: layout duration 0ms 0ms +0%
subgraph-dom-widget-clipping: style recalc duration 8ms 11ms +46% z=-1.8
subgraph-dom-widget-clipping: layout count 0 0 +0%
subgraph-dom-widget-clipping: style recalc count 47 47 +0% z=-1.6
subgraph-dom-widget-clipping: task duration 314ms 410ms +31% z=1.7
subgraph-dom-widget-clipping: script duration 85ms 122ms +43% z=-1.1
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: heap used 51.1 MB 51.5 MB +1%
subgraph-dom-widget-clipping: DOM nodes 20 20 +0% z=-1.9
subgraph-dom-widget-clipping: event listeners 8 8 +0% z=-1.4
subgraph-idle: avg frame time 17ms 17ms +0% z=0.9
subgraph-idle: p95 frame time 17ms 17ms +0%
subgraph-idle: layout duration 0ms 0ms +0%
subgraph-idle: style recalc duration 6ms 9ms +50% z=-2.4
subgraph-idle: layout count 0 0 +0%
subgraph-idle: style recalc count 10 10 -5% z=-2.1
subgraph-idle: task duration 401ms 460ms +15% ⚠️ z=2.9
subgraph-idle: script duration 12ms 13ms +12% z=-2.5
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: heap used 67.1 MB 67.2 MB +0%
subgraph-idle: DOM nodes 20 19 -5% z=-1.9
subgraph-idle: event listeners 6 4 -33% variance too high
subgraph-mouse-sweep: avg frame time 17ms 17ms +0% z=0.4
subgraph-mouse-sweep: p95 frame time 17ms 17ms -1%
subgraph-mouse-sweep: layout duration 3ms 4ms +24% z=-2.7
subgraph-mouse-sweep: style recalc duration 26ms 34ms +29% z=-2.6
subgraph-mouse-sweep: layout count 16 16 +0%
subgraph-mouse-sweep: style recalc count 76 76 +0% z=-2.1
subgraph-mouse-sweep: task duration 632ms 779ms +23% z=0.2
subgraph-mouse-sweep: script duration 61ms 91ms +48% z=-1.5
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: heap used 44.0 MB 51.3 MB +16%
subgraph-mouse-sweep: DOM nodes 4 -108 -2788% z=-78.1
subgraph-mouse-sweep: event listeners -86 -75 -13% variance too high
subgraph-transition-enter: avg frame time 17ms 17ms -0%
subgraph-transition-enter: p95 frame time 17ms 17ms +0%
subgraph-transition-enter: layout duration 9ms 12ms +30%
subgraph-transition-enter: style recalc duration 25ms 29ms +16%
subgraph-transition-enter: layout count 15 13 -13%
subgraph-transition-enter: style recalc count 19 17 -11%
subgraph-transition-enter: task duration 783ms 903ms +15%
subgraph-transition-enter: script duration 24ms 39ms +65%
subgraph-transition-enter: TBT 114ms 140ms +23%
subgraph-transition-enter: heap used 97.4 MB 96.7 MB -1%
subgraph-transition-enter: DOM nodes 13673 13673 +0%
subgraph-transition-enter: event listeners 2375 2375 +0%
viewport-pan-sweep: avg frame time 17ms 17ms +0%
viewport-pan-sweep: p95 frame time 17ms 17ms +0%
viewport-pan-sweep: layout duration 0ms 0ms +0%
viewport-pan-sweep: style recalc duration 23ms 37ms +57%
viewport-pan-sweep: layout count 0 0 +0%
viewport-pan-sweep: style recalc count 249 250 +0%
viewport-pan-sweep: task duration 3383ms 4570ms +35%
viewport-pan-sweep: script duration 952ms 1433ms +50%
viewport-pan-sweep: TBT 0ms 0ms +0%
viewport-pan-sweep: heap used 67.1 MB 67.3 MB +0%
viewport-pan-sweep: DOM nodes -282 -282 -0%
viewport-pan-sweep: event listeners -133 -132 -1%
vue-large-graph-idle: avg frame time 17ms 18ms +3%
vue-large-graph-idle: p95 frame time 17ms 17ms +0%
vue-large-graph-idle: layout duration 0ms 0ms +0%
vue-large-graph-idle: style recalc duration 0ms 0ms +0%
vue-large-graph-idle: layout count 0 0 +0%
vue-large-graph-idle: style recalc count 0 0 +0%
vue-large-graph-idle: task duration 11568ms 16903ms +46%
vue-large-graph-idle: script duration 437ms 589ms +35%
vue-large-graph-idle: TBT 0ms 0ms +0%
vue-large-graph-idle: heap used 159.7 MB 158.9 MB -1%
vue-large-graph-idle: DOM nodes -8312 -8314 +0%
vue-large-graph-idle: event listeners -16389 -16387 -0%
vue-large-graph-pan: avg frame time 17ms 18ms +7%
vue-large-graph-pan: p95 frame time 17ms 17ms +0%
vue-large-graph-pan: layout duration 0ms 0ms +0%
vue-large-graph-pan: style recalc duration 11ms 17ms +44%
vue-large-graph-pan: layout count 0 0 +0%
vue-large-graph-pan: style recalc count 68 143 +110%
vue-large-graph-pan: task duration 14179ms 20611ms +45%
vue-large-graph-pan: script duration 668ms 929ms +39%
vue-large-graph-pan: TBT 48ms 141ms +194%
vue-large-graph-pan: heap used 156.5 MB 160.0 MB +2%
vue-large-graph-pan: DOM nodes -8312 -8312 +0%
vue-large-graph-pan: event listeners -16387 -16384 -0%
workflow-execution: avg frame time 17ms 17ms -0% z=0.1
workflow-execution: p95 frame time 17ms 17ms +0%
workflow-execution: layout duration 0ms 1ms +123% z=-5.9
workflow-execution: style recalc duration 12ms 18ms +45% z=-2.9
workflow-execution: layout count 2 3 +50% z=-3.6
workflow-execution: style recalc count 14 12 -18% z=-3.1
workflow-execution: task duration 80ms 107ms +34% z=-1.5
workflow-execution: script duration 7ms 9ms +35% z=-6.7
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: heap used 65.4 MB 65.3 MB -0%
workflow-execution: DOM nodes 125 123 -2% z=-5.4
workflow-execution: event listeners 99 98 -1% z=10.6
Historical variance (last 15 runs)
Metric μ σ CV
canvas-idle: avg frame time 17ms 0ms 0.0%
canvas-idle: layout duration 0ms 0ms 0.0%
canvas-idle: style recalc duration 11ms 1ms 8.2%
canvas-idle: layout count 0 0 0.0%
canvas-idle: style recalc count 11 1 5.0%
canvas-idle: task duration 395ms 31ms 7.9%
canvas-idle: script duration 25ms 2ms 8.8%
canvas-idle: TBT 0ms 0ms 0.0%
canvas-idle: DOM nodes 23 1 5.6%
canvas-idle: event listeners 12 5 40.9%
canvas-mouse-sweep: avg frame time 17ms 0ms 0.0%
canvas-mouse-sweep: layout duration 4ms 0ms 5.4%
canvas-mouse-sweep: style recalc duration 43ms 3ms 7.4%
canvas-mouse-sweep: layout count 12 0 0.0%
canvas-mouse-sweep: style recalc count 79 2 3.0%
canvas-mouse-sweep: task duration 865ms 58ms 6.7%
canvas-mouse-sweep: script duration 136ms 6ms 4.8%
canvas-mouse-sweep: TBT 0ms 0ms 0.0%
canvas-mouse-sweep: DOM nodes 62 3 4.2%
canvas-mouse-sweep: event listeners 8 4 49.4%
canvas-zoom-sweep: avg frame time 17ms 0ms 0.0%
canvas-zoom-sweep: layout duration 1ms 0ms 7.0%
canvas-zoom-sweep: style recalc duration 19ms 2ms 8.0%
canvas-zoom-sweep: layout count 6 0 0.0%
canvas-zoom-sweep: style recalc count 31 0 1.5%
canvas-zoom-sweep: task duration 327ms 23ms 7.1%
canvas-zoom-sweep: script duration 27ms 3ms 11.1%
canvas-zoom-sweep: TBT 0ms 0ms 0.0%
canvas-zoom-sweep: DOM nodes 79 1 1.0%
canvas-zoom-sweep: event listeners 24 5 21.8%
dom-widget-clipping: avg frame time 17ms 0ms 0.0%
dom-widget-clipping: layout duration 0ms 0ms 0.0%
dom-widget-clipping: style recalc duration 10ms 1ms 8.0%
dom-widget-clipping: layout count 0 0 0.0%
dom-widget-clipping: style recalc count 13 0 3.8%
dom-widget-clipping: task duration 365ms 16ms 4.5%
dom-widget-clipping: script duration 68ms 3ms 4.8%
dom-widget-clipping: TBT 0ms 0ms 0.0%
dom-widget-clipping: DOM nodes 22 1 6.4%
dom-widget-clipping: event listeners 8 6 81.2%
large-graph-idle: avg frame time 17ms 0ms 0.0%
large-graph-idle: layout duration 0ms 0ms 0.0%
large-graph-idle: style recalc duration 12ms 1ms 8.6%
large-graph-idle: layout count 0 0 0.0%
large-graph-idle: style recalc count 12 0 2.7%
large-graph-idle: task duration 542ms 54ms 10.0%
large-graph-idle: script duration 102ms 11ms 10.3%
large-graph-idle: TBT 0ms 0ms 0.0%
large-graph-idle: DOM nodes 25 1 3.7%
large-graph-idle: event listeners 26 6 23.2%
large-graph-pan: avg frame time 17ms 0ms 0.0%
large-graph-pan: layout duration 0ms 0ms 0.0%
large-graph-pan: style recalc duration 17ms 1ms 4.6%
large-graph-pan: layout count 0 0 0.0%
large-graph-pan: style recalc count 70 1 0.9%
large-graph-pan: task duration 1082ms 43ms 4.0%
large-graph-pan: script duration 408ms 20ms 4.8%
large-graph-pan: TBT 0ms 0ms 0.0%
large-graph-pan: DOM nodes 19 2 8.7%
large-graph-pan: event listeners 5 1 16.8%
minimap-idle: avg frame time 17ms 0ms 0.0%
minimap-idle: layout duration 0ms 0ms 0.0%
minimap-idle: style recalc duration 10ms 1ms 8.6%
minimap-idle: layout count 0 0 0.0%
minimap-idle: style recalc count 10 1 7.1%
minimap-idle: task duration 527ms 47ms 9.0%
minimap-idle: script duration 98ms 10ms 10.1%
minimap-idle: TBT 0ms 0ms 0.0%
minimap-idle: DOM nodes 19 1 7.1%
minimap-idle: event listeners 5 1 14.4%
subgraph-dom-widget-clipping: avg frame time 17ms 0ms 0.0%
subgraph-dom-widget-clipping: layout duration 0ms 0ms 0.0%
subgraph-dom-widget-clipping: style recalc duration 13ms 1ms 7.4%
subgraph-dom-widget-clipping: layout count 0 0 0.0%
subgraph-dom-widget-clipping: style recalc count 48 1 1.2%
subgraph-dom-widget-clipping: task duration 378ms 18ms 4.9%
subgraph-dom-widget-clipping: script duration 128ms 6ms 4.9%
subgraph-dom-widget-clipping: TBT 0ms 0ms 0.0%
subgraph-dom-widget-clipping: DOM nodes 22 1 5.0%
subgraph-dom-widget-clipping: event listeners 16 6 36.0%
subgraph-idle: avg frame time 17ms 0ms 0.0%
subgraph-idle: layout duration 0ms 0ms 0.0%
subgraph-idle: style recalc duration 10ms 1ms 7.5%
subgraph-idle: layout count 0 0 0.0%
subgraph-idle: style recalc count 11 1 6.0%
subgraph-idle: task duration 370ms 31ms 8.5%
subgraph-idle: script duration 20ms 3ms 13.2%
subgraph-idle: TBT 0ms 0ms 0.0%
subgraph-idle: DOM nodes 22 1 6.9%
subgraph-idle: event listeners 10 7 64.5%
subgraph-mouse-sweep: avg frame time 17ms 0ms 0.0%
subgraph-mouse-sweep: layout duration 5ms 0ms 6.8%
subgraph-mouse-sweep: style recalc duration 42ms 3ms 7.8%
subgraph-mouse-sweep: layout count 16 0 0.0%
subgraph-mouse-sweep: style recalc count 80 2 2.4%
subgraph-mouse-sweep: task duration 766ms 69ms 9.0%
subgraph-mouse-sweep: script duration 101ms 7ms 6.5%
subgraph-mouse-sweep: TBT 0ms 0ms 0.0%
subgraph-mouse-sweep: DOM nodes 67 2 3.3%
subgraph-mouse-sweep: event listeners 8 4 52.6%
workflow-execution: avg frame time 17ms 0ms 0.0%
workflow-execution: layout duration 2ms 0ms 9.4%
workflow-execution: style recalc duration 24ms 2ms 9.1%
workflow-execution: layout count 5 1 11.0%
workflow-execution: style recalc count 18 2 11.5%
workflow-execution: task duration 123ms 11ms 8.8%
workflow-execution: script duration 29ms 3ms 10.2%
workflow-execution: TBT 0ms 0ms 0.0%
workflow-execution: DOM nodes 161 7 4.4%
workflow-execution: event listeners 52 4 8.4%
Trend (last 15 commits on main)
Metric Trend Dir Latest
canvas-idle: avg frame time ▆▃▆▁▆▃▆█▆▆▄▃▃▄▃ ➡️ 17ms
canvas-idle: p95 frame time ➡️ NaNms
canvas-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-idle: style recalc duration ▇▇▆▆▃█▄▃▄▃▇▄▁▆▇ ➡️ 11ms
canvas-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
canvas-idle: style recalc count █▃▅▂▅▆▃▁▂▁▂▅▆▅▆ ➡️ 12
canvas-idle: task duration ▃▃▃▆▂▃▃▅▆▂█▃▁▃▃ ➡️ 391ms
canvas-idle: script duration ▄▃▅▇▂▅▃▆▇▅█▄▁▅▆ ➡️ 27ms
canvas-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-idle: heap used ➡️ NaN MB
canvas-idle: DOM nodes █▇▆▅▃▇▃▁▂▂▅▆▆▆▇ ➡️ 24
canvas-idle: event listeners ▅█▅▄▁▅▁▁▁▄▅▅▁▅▄ 📉 11
canvas-mouse-sweep: avg frame time ▆█▆▃▁▃▁▆▆▁▃▆▆▃▃ ➡️ 17ms
canvas-mouse-sweep: p95 frame time ➡️ NaNms
canvas-mouse-sweep: layout duration ▁▃▂▄▁▂▁▃▆▂█▇▆▄▃ ➡️ 4ms
canvas-mouse-sweep: style recalc duration ▄▄▂▄▁▂▃▃▅▄█▆▂▄▄ ➡️ 43ms
canvas-mouse-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 12
canvas-mouse-sweep: style recalc count █▅▄▃▂▂▁▄▄▅▆▅▂▇▄ ➡️ 79
canvas-mouse-sweep: task duration █▆▄▂▂▃▂▄▄▅█▆▁▆▄ ➡️ 868ms
canvas-mouse-sweep: script duration ▄▅▄▆▄▆▆▆▅▅█▆▁▅▆ ➡️ 139ms
canvas-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-mouse-sweep: heap used ➡️ NaN MB
canvas-mouse-sweep: DOM nodes █▅▃▃▁▂▂▃▂▄▆▅▃▅▅ ➡️ 64
canvas-mouse-sweep: event listeners █▁▁▁▁▁▇▁▁▁██▇▁█ 📈 13
canvas-zoom-sweep: avg frame time ▅▅█▄▅▁▁▁▅▁▁▅▄▅▁ ➡️ 17ms
canvas-zoom-sweep: p95 frame time ➡️ NaNms
canvas-zoom-sweep: layout duration ▆▅▅▄▁▁█▅▃▅▇▆▁▂▆ ➡️ 1ms
canvas-zoom-sweep: style recalc duration ▆▅▄▆▅▃█▆▇▅▇▄▁▃▅ ➡️ 20ms
canvas-zoom-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 6
canvas-zoom-sweep: style recalc count ▁▁▃▄▆▃▆█▄▄▆▁▆▁▆ ➡️ 32
canvas-zoom-sweep: task duration ▄▂▁▇▂▂▄▅▆▃█▄▁▁▅ ➡️ 338ms
canvas-zoom-sweep: script duration ▃▃▂▇▂▂▅▇▆▅█▄▁▂▆ ➡️ 30ms
canvas-zoom-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-zoom-sweep: heap used ➡️ NaN MB
canvas-zoom-sweep: DOM nodes ▄▃▁▅█▁▃▆▄▅▅▃▃▄▃ ➡️ 79
canvas-zoom-sweep: event listeners ▁▁▂▅█▂▁▅▁▅▅▄▁▅▁ ➡️ 19
dom-widget-clipping: avg frame time ▂▄▅▅▂▄█▇▅▇▇▅▅▁▇ ➡️ 17ms
dom-widget-clipping: p95 frame time ➡️ NaNms
dom-widget-clipping: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
dom-widget-clipping: style recalc duration ▆▆▂▆▄▃██▄▁▆▇▆▃▅ ➡️ 10ms
dom-widget-clipping: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
dom-widget-clipping: style recalc count ▇█▅█▅▄█▇▇▁▇▄▇▂▅ ➡️ 13
dom-widget-clipping: task duration ▃▃▁▅▄▃▅▆▅▂▇█▁▅▅ ➡️ 371ms
dom-widget-clipping: script duration ▅▄▄▆▆▅▇▇▆▃█▇▁▇▇ ➡️ 71ms
dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
dom-widget-clipping: heap used ➡️ NaN MB
dom-widget-clipping: DOM nodes ▇▇▄▇▅▄█▇▅▁▅▄▇▃▄ ➡️ 21
dom-widget-clipping: event listeners ▅▅▅▅▁▅██▁▁▁▁█▁▁ 📉 2
large-graph-idle: avg frame time ▅▅▅▅▅▂▁▂▄▅▄▂▂▅█ ➡️ 17ms
large-graph-idle: p95 frame time ➡️ NaNms
large-graph-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-idle: style recalc duration ▅▅▅▆▄▅▃▄▅▅▆█▁▄▆ ➡️ 13ms
large-graph-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-idle: style recalc count █▆█▃▃▁▃▆▃▆▆▃▆██ ➡️ 12
large-graph-idle: task duration ▂▃▂▆▂▃▃▇▅▃██▁▂▅ ➡️ 569ms
large-graph-idle: script duration ▄▅▄▆▄▅▅▇▆▅█▆▁▃▆ ➡️ 110ms
large-graph-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-idle: heap used ➡️ NaN MB
large-graph-idle: DOM nodes ▆█▅▂▅▃▁▂▃▅▅▆▂▆▅ ➡️ 25
large-graph-idle: event listeners ███▇██▄▁▄▇▇█▂█▇ ➡️ 29
large-graph-pan: avg frame time ▆▃▃▆█▃▁█▆▆▆▆█▁▆ ➡️ 17ms
large-graph-pan: p95 frame time ➡️ NaNms
large-graph-pan: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-pan: style recalc duration ▃▂▄▄▁▅▂▂▁▄▄█▃▁▂ ➡️ 17ms
large-graph-pan: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-pan: style recalc count ▆▃█▂▃▂▂▂▁▇▅▃█▆▃ ➡️ 69
large-graph-pan: task duration ▄▃▄▆▄▄▄▆▄▄█▆▁▂▅ ➡️ 1100ms
large-graph-pan: script duration ▅▄▅▆▆▅▄▆▄▅█▄▁▄▅ ➡️ 413ms
large-graph-pan: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-pan: heap used ➡️ NaN MB
large-graph-pan: DOM nodes ▅▃▆▂▄▁▃▁▁▅▁▂█▅▂ ➡️ 18
large-graph-pan: event listeners █▆█▁▁▆▁▁▃▆▁▃██▃ ➡️ 5
minimap-idle: avg frame time ▃▆▆▃█▁█▆▆▃▃▆█▆█ ➡️ 17ms
minimap-idle: p95 frame time ➡️ NaNms
minimap-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
minimap-idle: style recalc duration ▄█▁█▅▅█▅▅▃▅▁▁▄▆ ➡️ 10ms
minimap-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
minimap-idle: style recalc count ▃▅▂▄█▃▆▁▂▅▂▁▅▆▃ ➡️ 9
minimap-idle: task duration ▃▄▁▅▁▃▄▅▇▃█▅▁▁▅ ➡️ 547ms
minimap-idle: script duration ▄▆▃▇▃▅▆▆▇▅█▅▁▃▆ ➡️ 106ms
minimap-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
minimap-idle: heap used ➡️ NaN MB
minimap-idle: DOM nodes ▃▅▂▄█▃▆▁▂▅▂▁▅▆▃ ➡️ 19
minimap-idle: event listeners ▃▃▆▁▁▁▃▁▁▆▁▃█▆▁ ➡️ 4
subgraph-dom-widget-clipping: avg frame time ▅▄▄▄▄▄█▄▄▄▃▁▆▃▃ ➡️ 17ms
subgraph-dom-widget-clipping: p95 frame time ➡️ NaNms
subgraph-dom-widget-clipping: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-dom-widget-clipping: style recalc duration ▂▄▃▅▅▃▂▅▇▃▄█▁▄▆ ➡️ 14ms
subgraph-dom-widget-clipping: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-dom-widget-clipping: style recalc count ▇█▆▃▆▃▁▆█▇▃▆▇█▅ ➡️ 48
subgraph-dom-widget-clipping: task duration ▂▃▃▆▅▅▂▅█▂▆█▁▂▇ ➡️ 398ms
subgraph-dom-widget-clipping: script duration ▃▃▃▄▅▅▂▄█▂▅▇▁▂▅ ➡️ 131ms
subgraph-dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-dom-widget-clipping: heap used ➡️ NaN MB
subgraph-dom-widget-clipping: DOM nodes ▅▇▅▂▅▂▁▅▅▅▁▇▅█▄ ➡️ 22
subgraph-dom-widget-clipping: event listeners ▅▅▅▂▅▁▅██▁▁█▅█▅ 📈 16
subgraph-idle: avg frame time ▆▆█▁▆▃▆▆▆▃▆▁▃▆█ ➡️ 17ms
subgraph-idle: p95 frame time ➡️ NaNms
subgraph-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-idle: style recalc duration ▁▇▃▆▂▄▂▃▃▆▆▄▃▇█ ➡️ 12ms
subgraph-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-idle: style recalc count ▃▆▃▃▂▅▁▂▁▆▃▃██▇ ➡️ 12
subgraph-idle: task duration ▁▃▁▇▁▁▃▆▅▂█▅▁▁▄ ➡️ 378ms
subgraph-idle: script duration ▁▃▂▇▁▂▃▇▆▂█▅▂▁▅ ➡️ 22ms
subgraph-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-idle: heap used ➡️ NaN MB
subgraph-idle: DOM nodes ▃▅▃▂▁▄▁▂▁▅▃▂▇█▇ ➡️ 24
subgraph-idle: event listeners ▁▅▁▁▁▁▁▁▁▅▄▁███ 📈 21
subgraph-mouse-sweep: avg frame time ▅▄▁▃▃▄▆▄▆▃▃█▁▃▃ ➡️ 17ms
subgraph-mouse-sweep: p95 frame time ➡️ NaNms
subgraph-mouse-sweep: layout duration ▁▄▄▄▃▃▅▅▅▂█▇▂▃▆ ➡️ 5ms
subgraph-mouse-sweep: style recalc duration ▃▂▄▅▂▃▄▅█▃█▆▁▂▅ ➡️ 43ms
subgraph-mouse-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 16
subgraph-mouse-sweep: style recalc count ▅▂▅▅▁▄▃▅█▅▆▄▂▄▅ ➡️ 81
subgraph-mouse-sweep: task duration ▃▂▄▅▂▄▄▅▇▄█▆▁▃▅ ➡️ 785ms
subgraph-mouse-sweep: script duration ▄▅▄▇▅▅▆▇▆▅██▁▄▆ ➡️ 105ms
subgraph-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-mouse-sweep: heap used ➡️ NaN MB
subgraph-mouse-sweep: DOM nodes ▅▁▄▅▁▄▃▃█▅▅▄▂▅▃ ➡️ 66
subgraph-mouse-sweep: event listeners ▇▁▂▇▁▂▂▂█▇▂▂▇▇▂ 📈 5
workflow-execution: avg frame time ▆▆▆▄▆▆▃▄▁▄█▆▅▄▆ ➡️ 17ms
workflow-execution: p95 frame time ➡️ NaNms
workflow-execution: layout duration ▁▆▁▃▂▄▃▂▃▃▅█▄▂▅ ➡️ 2ms
workflow-execution: style recalc duration ▃▇▅▇▁▅▆▇█▁██▂▄▆ ➡️ 25ms
workflow-execution: layout count ▁█▂▃▂▃▃▁▃▃▄▃▂▃▂ ➡️ 5
workflow-execution: style recalc count ▃█▅▇▁▄▅▆▅▅▅▅▄▄▂ ➡️ 15
workflow-execution: task duration ▂▅▄▅▁▄▆▆▆▁▇█▁▃▃ ➡️ 120ms
workflow-execution: script duration ▄▃▄▄▃▅▄▅▆▂▇█▁▃▄ ➡️ 29ms
workflow-execution: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
workflow-execution: heap used ➡️ NaN MB
workflow-execution: DOM nodes ▂█▃▆▁▄▃▅▃█▃▃▄▃▁ ➡️ 152
workflow-execution: event listeners ▅███▁▅███▁██▅█▅ ➡️ 49
Raw data
{
  "timestamp": "2026-08-09T01:23:09.453Z",
  "gitSha": "ccb7449b5d476660aea1ffecf46853d818fca16e",
  "branch": "cb__release-stranded-guard",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2037.3289999999997,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.116999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 516.452,
      "heapDeltaBytes": 5872180,
      "heapUsedBytes": 70323132,
      "domNodes": 18,
      "jsHeapTotalBytes": 24641536,
      "scriptDurationMs": 21.334,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-idle",
      "durationMs": 2039.0129999999544,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 8.527999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 473.28799999999995,
      "heapDeltaBytes": 5796392,
      "heapUsedBytes": 70318476,
      "domNodes": 20,
      "jsHeapTotalBytes": 24903680,
      "scriptDurationMs": 15.010000000000003,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1988.1409999999846,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 42.508,
      "layouts": 12,
      "layoutDurationMs": 4.8549999999999995,
      "taskDurationMs": 979.4590000000001,
      "heapDeltaBytes": 584776,
      "heapUsedBytes": 64897304,
      "domNodes": 59,
      "jsHeapTotalBytes": 25165824,
      "scriptDurationMs": 132.602,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1822.0640000000685,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 35.17,
      "layouts": 12,
      "layoutDurationMs": 3.3229999999999995,
      "taskDurationMs": 856.929,
      "heapDeltaBytes": -13574088,
      "heapUsedBytes": 50931108,
      "domNodes": -280,
      "jsHeapTotalBytes": 23568384,
      "scriptDurationMs": 119.48,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1755.3859999999872,
      "styleRecalcs": 32,
      "styleRecalcDurationMs": 18.555000000000003,
      "layouts": 6,
      "layoutDurationMs": 0.685,
      "taskDurationMs": 389.767,
      "heapDeltaBytes": 8810548,
      "heapUsedBytes": 73286140,
      "domNodes": 77,
      "jsHeapTotalBytes": 24379392,
      "scriptDurationMs": 19.932000000000002,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1732.0700000000215,
      "styleRecalcs": 31,
      "styleRecalcDurationMs": 18.268,
      "layouts": 6,
      "layoutDurationMs": 0.551,
      "taskDurationMs": 407.78900000000004,
      "heapDeltaBytes": 8861400,
      "heapUsedBytes": 73192232,
      "domNodes": 76,
      "jsHeapTotalBytes": 24379392,
      "scriptDurationMs": 22.607000000000003,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 590.0820000000522,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 8.559,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 377.81600000000003,
      "heapDeltaBytes": -11255184,
      "heapUsedBytes": 53240728,
      "domNodes": 18,
      "jsHeapTotalBytes": 24379392,
      "scriptDurationMs": 60.923,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 589.3980000000738,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 7.924,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 375.51800000000003,
      "heapDeltaBytes": -11244372,
      "heapUsedBytes": 53136476,
      "domNodes": 20,
      "jsHeapTotalBytes": 25165824,
      "scriptDurationMs": 59.18599999999999,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2039.2870000000016,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 9.495,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 818.8570000000002,
      "heapDeltaBytes": 6952908,
      "heapUsedBytes": 66285120,
      "domNodes": -280,
      "jsHeapTotalBytes": 3510272,
      "scriptDurationMs": 128.777,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2011.5749999999935,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.679000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 659.344,
      "heapDeltaBytes": 6220416,
      "heapUsedBytes": 65930228,
      "domNodes": -284,
      "jsHeapTotalBytes": 2985984,
      "scriptDurationMs": 97.501,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2256.7960000000085,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 14.508000000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1382.942,
      "heapDeltaBytes": 4751972,
      "heapUsedBytes": 65610992,
      "domNodes": -285,
      "jsHeapTotalBytes": 3440640,
      "scriptDurationMs": 461.526,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2214.7059999999783,
      "styleRecalcs": 69,
      "styleRecalcDurationMs": 14.286,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1299.049,
      "heapDeltaBytes": 4425420,
      "heapUsedBytes": 65000380,
      "domNodes": -284,
      "jsHeapTotalBytes": 3178496,
      "scriptDurationMs": 435.704,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3229.8210000000154,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 17.841,
      "layouts": 60,
      "layoutDurationMs": 7.206,
      "taskDurationMs": 1534.9089999999999,
      "heapDeltaBytes": -1950756,
      "heapUsedBytes": 60194484,
      "domNodes": -286,
      "jsHeapTotalBytes": 6131712,
      "scriptDurationMs": 536.323,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3214.2760000000408,
      "styleRecalcs": 66,
      "styleRecalcDurationMs": 15.546999999999999,
      "layouts": 60,
      "layoutDurationMs": 7.136,
      "taskDurationMs": 1493.979,
      "heapDeltaBytes": -2096296,
      "heapUsedBytes": 60110004,
      "domNodes": -288,
      "jsHeapTotalBytes": 6131712,
      "scriptDurationMs": 523.139,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2027.1169999999756,
      "styleRecalcs": 7,
      "styleRecalcDurationMs": 6.186000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 653.7790000000001,
      "heapDeltaBytes": 7195980,
      "heapUsedBytes": 68245528,
      "domNodes": -286,
      "jsHeapTotalBytes": 3510272,
      "scriptDurationMs": 99.25900000000001,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "minimap-idle",
      "durationMs": 2041.729000000032,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 8.863,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 717.649,
      "heapDeltaBytes": 7110920,
      "heapUsedBytes": 68260280,
      "domNodes": -285,
      "jsHeapTotalBytes": 1413120,
      "scriptDurationMs": 118.82699999999998,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 597.3600000000374,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 10.825000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 401.466,
      "heapDeltaBytes": -10715292,
      "heapUsedBytes": 53885540,
      "domNodes": 20,
      "jsHeapTotalBytes": 26214400,
      "scriptDurationMs": 119.175,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 578.5939999999528,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 11.161000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 417.84900000000005,
      "heapDeltaBytes": -10632448,
      "heapUsedBytes": 54154552,
      "domNodes": 20,
      "jsHeapTotalBytes": 25690112,
      "scriptDurationMs": 123.96799999999999,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2003.877999999986,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 9.833,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 459.48199999999997,
      "heapDeltaBytes": 5905016,
      "heapUsedBytes": 70437596,
      "domNodes": 18,
      "jsHeapTotalBytes": 24379392,
      "scriptDurationMs": 14.016,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1997.7579999999762,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 7.3069999999999995,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 461.318,
      "heapDeltaBytes": 5859344,
      "heapUsedBytes": 70479000,
      "domNodes": 20,
      "jsHeapTotalBytes": 24903680,
      "scriptDurationMs": 12.928999999999998,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1696.4399999999955,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 33.456,
      "layouts": 16,
      "layoutDurationMs": 3.8729999999999998,
      "taskDurationMs": 811.7810000000001,
      "heapDeltaBytes": -17647876,
      "heapUsedBytes": 46716916,
      "domNodes": -279,
      "jsHeapTotalBytes": 23568384,
      "scriptDurationMs": 91.269,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1702.4940000000015,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 34.455,
      "layouts": 16,
      "layoutDurationMs": 3.797,
      "taskDurationMs": 745.232,
      "heapDeltaBytes": -3880260,
      "heapUsedBytes": 60788040,
      "domNodes": 64,
      "jsHeapTotalBytes": 24379392,
      "scriptDurationMs": 90.423,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-transition-enter",
      "durationMs": 1417.385999999965,
      "styleRecalcs": 17,
      "styleRecalcDurationMs": 29.375999999999998,
      "layouts": 13,
      "layoutDurationMs": 12.193,
      "taskDurationMs": 903.06,
      "heapDeltaBytes": 31071292,
      "heapUsedBytes": 101398544,
      "domNodes": 13673,
      "jsHeapTotalBytes": 15204352,
      "scriptDurationMs": 39.399,
      "eventListeners": 2375,
      "totalBlockingTimeMs": 140,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8315.846000000021,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 36.589,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4616.339,
      "heapDeltaBytes": 10290260,
      "heapUsedBytes": 69808256,
      "domNodes": -281,
      "jsHeapTotalBytes": 6848512,
      "scriptDurationMs": 1475.198,
      "eventListeners": -133,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.80000000000109
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8437.779999999975,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 36.896,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4523.509000000001,
      "heapDeltaBytes": 11962788,
      "heapUsedBytes": 71378884,
      "domNodes": -282,
      "jsHeapTotalBytes": 6324224,
      "scriptDurationMs": 1390.27,
      "eventListeners": -131,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 16769.893000000025,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 16721.397000000004,
      "heapDeltaBytes": -49599172,
      "heapUsedBytes": 165642764,
      "domNodes": -8316,
      "jsHeapTotalBytes": -10690560,
      "scriptDurationMs": 585.448,
      "eventListeners": -16387,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.776666666666763,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 17127.395999999975,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 17084.074,
      "heapDeltaBytes": -61689188,
      "heapUsedBytes": 167644384,
      "domNodes": -8312,
      "jsHeapTotalBytes": -8069120,
      "scriptDurationMs": 592.667,
      "eventListeners": -16387,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.779999999999927,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 20699.111999999957,
      "styleRecalcs": 143,
      "styleRecalcDurationMs": 16.488999999999976,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 20654.85,
      "heapDeltaBytes": -57417068,
      "heapUsedBytes": 166245172,
      "domNodes": -8312,
      "jsHeapTotalBytes": -10166272,
      "scriptDurationMs": 912.778,
      "eventListeners": -16381,
      "totalBlockingTimeMs": 175,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 20600.782999999978,
      "styleRecalcs": 143,
      "styleRecalcDurationMs": 16.568000000000026,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 20567.954999999998,
      "heapDeltaBytes": -37937552,
      "heapUsedBytes": 169229852,
      "domNodes": -8312,
      "jsHeapTotalBytes": -8822784,
      "scriptDurationMs": 945.296,
      "eventListeners": -16387,
      "totalBlockingTimeMs": 107,
      "frameDurationMs": 17.780000000000047,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "workflow-execution",
      "durationMs": 445.6750000000511,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 17.575,
      "layouts": 3,
      "layoutDurationMs": 0.712,
      "taskDurationMs": 105.558,
      "heapDeltaBytes": 5061380,
      "heapUsedBytes": 68610320,
      "domNodes": 124,
      "jsHeapTotalBytes": 4980736,
      "scriptDurationMs": 9.198,
      "eventListeners": 99,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "workflow-execution",
      "durationMs": 469.8929999999564,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 18.288,
      "layouts": 3,
      "layoutDurationMs": 0.6679999999999999,
      "taskDurationMs": 108.307,
      "heapDeltaBytes": 5050880,
      "heapUsedBytes": 68417724,
      "domNodes": 121,
      "jsHeapTotalBytes": 5242880,
      "scriptDurationMs": 9.344,
      "eventListeners": 97,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.699999999999818
    }
  ]
}

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 36 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4a3fc3bc-bc29-468c-a61a-307f5fd55290

📥 Commits

Reviewing files that changed from the base of the PR and between 916e085 and bce0b29.

📒 Files selected for processing (4)
  • .github/workflows/backport-auto-merge.yaml
  • .github/workflows/release-stranded-check.yaml
  • scripts/cicd/check-stranded-release-commits.test.ts
  • scripts/cicd/check-stranded-release-commits.ts
📝 Walkthrough

Walkthrough

Changes

Stranded release detection

Layer / File(s) Summary
Release-line contracts and evaluation
scripts/cicd/check-stranded-release-commits.ts, scripts/cicd/check-stranded-release-commits.test.ts
Adds release contracts, commit classification, stranded-fix and version-lag evaluation, pin checks, and Vitest coverage.
Release metadata and result orchestration
scripts/cicd/check-stranded-release-commits.ts
Retrieves repository and PyPI metadata, evaluates consumed branches, reports findings, and sets outputs and exit status.
Scheduled workflow and failure handling
.github/workflows/release-stranded-check.yaml
Runs the checker, records reports, dispatches non-duplicate patch releases, and posts validated Slack failures when configured.
Release pull request merge support
.github/workflows/backport-auto-merge.yaml
Extends approved and scheduled auto-merge handling to pull requests labelled Release as well as backport.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant StrandedReleaseCheck
  participant GitHubRepository
  participant PyPI
  participant Slack
  GitHubActions->>StrandedReleaseCheck: run release-commit check
  StrandedReleaseCheck->>GitHubRepository: fetch release metadata and commits
  StrandedReleaseCheck->>PyPI: fetch published versions
  StrandedReleaseCheck-->>GitHubActions: return findings and exit status
  GitHubActions->>GitHubRepository: dispatch patch release when required
  GitHubActions->>Slack: post failure notification when configured
Loading

Possibly related PRs

Suggested labels: released:cloud

Suggested reviewers: drjkl


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
End-To-End Regression Coverage For Fixes ❓ Inconclusive The review context does not provide the PR title or exact commit subjects, so the required bug-fix signal cannot be verified. Provide the PR title and commit subjects, or confirm that no title or subject contains bug-fix language.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: failing CI when fixes remain unshipped on a consumed release line.
Description check ✅ Passed The description explains the problem, implementation, scope, verification, notification requirement, and issue reference in sufficient detail.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Adr Compliance For Entity/Litegraph Changes ✅ Passed The changed files are workflows and CI scripts/tests only; none are under src/lib/litegraph/, src/ecs/, or graph-entity files, so this check does not apply.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cb__release-stranded-guard

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release-stranded-check.yaml:
- Around line 70-76: Update the Slack request in the release workflow to capture
the chat.postMessage response instead of discarding it, add curl connection and
total timeouts, and parse the response with jq to require .ok == true. Preserve
continue-on-error behavior and ensure HTTP or Slack API failures cause the step
to fail.

In `@scripts/cicd/check-stranded-release-commits.test.ts`:
- Around line 1-8: Extend the imports in check-stranded-release-commits.test.ts
to include parsePinnedVersion and newestStableVersion, then add focused tests
covering valid and malformed requirements.txt pin lines plus PyPI version
selection, including unexpected or non-stable version formats. Preserve the
existing tests and verify the missing-version and relevant edge-case behavior
exposed by these pure functions.

In `@scripts/cicd/check-stranded-release-commits.ts`:
- Around line 188-194: Guard the git fetch call in the loop over linesToCheck so
a fetch failure for one branch is handled locally and skips that branch,
allowing subsequent branches to be evaluated. Preserve the existing latestTagOn
and no-tag continue behavior, and use the script’s established error-handling
pattern if available.
- Around line 118-122: Update fetchText to pass a finite timeout signal, such as
AbortSignal.timeout(...), to fetch so stalled external requests terminate
instead of hanging indefinitely. Preserve the existing HTTP status validation
and response-text behavior.
- Around line 114-158: Replace shell-interpreted command construction in git()
with execFileSync using a Git executable and argument arrays, then update
latestTagOn(), commitsPastTag(), and the git fetch call to pass arguments
separately. Preserve the existing Git operations and outputs while ensuring tag
and branch values are never interpolated into a shell command.
- Around line 160-168: Update the PyPI response parsing in main so JSON.parse
results are treated as unknown and narrowed with a type guard that verifies a
non-null object with a valid releases object before Object.keys is called.
Remove the direct `{ releases: Record<string, unknown> }` assertion, and provide
a clear diagnostic or failure when the response shape is invalid.
- Around line 221-226: Update the entry-point check around process.argv and
import.meta.url to avoid the non-null assertion and compare the intended script
path precisely, using a guard that safely handles a missing basename. Change the
main() invocation to attach an explicit catch handler that reports the failure
and exits with a nonzero status.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d73fd80a-ce39-4eea-bb8e-1dfe28630371

📥 Commits

Reviewing files that changed from the base of the PR and between 0e67635 and bb738b7.

📒 Files selected for processing (3)
  • .github/workflows/release-stranded-check.yaml
  • scripts/cicd/check-stranded-release-commits.test.ts
  • scripts/cicd/check-stranded-release-commits.ts

Comment thread .github/workflows/release-stranded-check.yaml Outdated
Comment thread scripts/cicd/check-stranded-release-commits.test.ts
Comment thread scripts/cicd/check-stranded-release-commits.ts Outdated
Comment thread scripts/cicd/check-stranded-release-commits.ts
Comment thread scripts/cicd/check-stranded-release-commits.ts
Comment thread scripts/cicd/check-stranded-release-commits.ts
Comment thread scripts/cicd/check-stranded-release-commits.ts
@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

@@            Coverage Diff             @@
##             main   #14429      +/-   ##
==========================================
+ Coverage   78.72%   78.74%   +0.01%     
==========================================
  Files        1866     1868       +2     
  Lines      115408   116073     +665     
  Branches    33539    34822    +1283     
==========================================
+ Hits        90860    91398     +538     
- Misses      24005    24123     +118     
- Partials      543      552       +9     
Flag Coverage Δ
unit 72.34% <ø> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 25 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 30, 2026
@christian-byrne

Copy link
Copy Markdown
Contributor Author

Relationship to #14039, which is open and should land: that PR adds a release-done assertion inside the biweekly, running once right after publish. This one runs daily and on release publish.

Different windows, and 1.47.10 shows why both matter. At publish time nothing was stranded, so an at-publish assertion passes. #14065 merged onto core/1.47 19 hours later, after the release run had finished, and nothing re-evaluated it until users reported the bug seven days on.

No file overlap: this adds a new workflow and script and does not touch release-biweekly-comfyui.yaml, so the two can land in either order.

@christian-byrne

Copy link
Copy Markdown
Contributor Author

Pushed a change of approach after a fair criticism: as written, this PR and #14039 only told a human that a fix was stranded. Both times this failed, the information already existed and nobody acted on it, so another alert was not the fix.

Two changes.

The check now dispatches the patch release for the line ComfyUI pins, rather than only failing and posting to Slack. It skips if a Release bump PR is already open against that branch, so it cannot resolve a second patch while one is in flight.

Version-bump PRs join backport auto-merge. That workflow already merges backport PRs once they are approved and green; version bumps are equally bot-authored and mechanical, and leaving them for a human to click is what timed out publish-pypi on both the 1.47.10 run and the 07-27 run, which also silently skipped the ComfyUI pin PR.

To be clear about what this does not do: branch protection still requires an approval and green checks on the bump PR, and GitHub re-enforces that at merge time. This removes the requirement that somebody remembers, not the requirement that somebody approves. Dropping the approval on bot-authored version bumps would close the loop entirely, but that is a branch-protection policy decision rather than a workflow change.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

♻️ Duplicate comments (2)
scripts/cicd/check-stranded-release-commits.ts (2)

214-220: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

git fetch failure aborts the whole run instead of skipping the branch.

latestTagOn tolerates a missing tag and continues to the next branch, but the git('fetch', ...) call above it is not guarded. If one release line's branch is missing, renamed, or unreachable on origin, execFileSync throws here, and the entire scheduled check aborts before evaluating the other consumed line. This mirrors a concern raised and reportedly addressed in an earlier round of review on this PR ("The fetch is wrapped and now skips that line"), but the current code shows the fetch call unguarded.

🛠️ Proposed fix
   for (const branch of linesToCheck) {
-    git('fetch', 'origin', `${branch}:refs/remotes/origin/${branch}`, '--tags')
+    try {
+      git('fetch', 'origin', `${branch}:refs/remotes/origin/${branch}`, '--tags')
+    } catch {
+      console.error(`${branch}: could not fetch, skipping`)
+      continue
+    }
     const latestTag = latestTagOn(branch)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/cicd/check-stranded-release-commits.ts` around lines 214 - 220, Guard
the git fetch call inside the loop over linesToCheck so failures for a missing,
renamed, or unreachable branch are caught and skipped. Update the flow around
git and latestTagOn to continue to the next branch after logging the fetch
failure, while preserving the existing no-tag handling for successful fetches.

191-194: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Unnarrowed type assertion on the PyPI response.

JSON.parse(t) as { releases: Record<string, unknown> } asserts the shape without validation. If PyPI returns an error body or a different shape, pypi.releases is undefined, and Object.keys(pypi.releases) on line 197 throws an opaque TypeError instead of a clear diagnostic. This mirrors a concern raised and reportedly addressed in an earlier round of review on this PR ("Narrowed with a guard that throws a named error instead of asserting the shape"), but the current code still shows the unguarded assertion.

🐛 Proposed fix: narrow with a guard instead of an assertion
     fetchText('https://pypi.org/pypi/comfyui-frontend-package/json').then(
-      (t) => JSON.parse(t) as { releases: Record<string, unknown> }
+      (t) => {
+        const parsed: unknown = JSON.parse(t)
+        if (
+          typeof parsed !== 'object' ||
+          parsed === null ||
+          typeof (parsed as { releases?: unknown }).releases !== 'object'
+        ) {
+          throw new Error('Unexpected PyPI response shape: missing "releases"')
+        }
+        return parsed as { releases: Record<string, unknown> }
+      }
     )

As per path instructions, docs/guidance/typescript.md requires: "do not use any, as any, or unnecessary type assertions; use explicit types and narrowing for commit, finding, version, and input data."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/cicd/check-stranded-release-commits.ts` around lines 191 - 194,
Replace the unvalidated JSON shape assertion in the PyPI response handling with
explicit parsing and a guard that verifies releases is a non-null object before
use. Throw a named, descriptive error when validation fails, then pass the
narrowed releases value to the existing Object.keys flow without using any or
unnecessary type assertions.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/backport-auto-merge.yaml:
- Around line 119-128: Rename the label predicate variable from is_backport to
has_supported_label to reflect both backport and Release labels. Update its
conditional usage and the related log field around the workflow’s PR filtering
logic so Release PRs are not reported as backport PRs.
- Around line 83-84: Update the scheduled sweep’s PR collection command in the
workflow to query `backport` and `Release` labels separately with OR semantics,
rather than passing both labels to one `gh pr list` invocation. Preserve the
existing result aggregation and de-duplication so pull requests matching either
label are processed once.

In `@scripts/cicd/check-stranded-release-commits.test.ts`:
- Around line 193-198: Remove the duplicate test named “does not fail merely
because a newer minor line exists” because its evaluatePin input is identical to
the existing current-pin test and cannot exercise main()’s release-line
filtering. Preserve the existing current-pin coverage, or replace this test with
distinct behavioral coverage for same-line pin lag or its failure-reporting
details using evaluatePin’s actual inputs.

In `@scripts/cicd/check-stranded-release-commits.ts`:
- Around line 214-230: Update the publishedVersion selection in main’s
linesToCheck loop to use the already-computed newestOnPinnedLine for the
pinned-line branch instead of the raw pinned value. Keep published for the
newest minor line, and ensure evaluateLine receives the actual latest
PyPI-published version corresponding to each branch.

---

Duplicate comments:
In `@scripts/cicd/check-stranded-release-commits.ts`:
- Around line 214-220: Guard the git fetch call inside the loop over
linesToCheck so failures for a missing, renamed, or unreachable branch are
caught and skipped. Update the flow around git and latestTagOn to continue to
the next branch after logging the fetch failure, while preserving the existing
no-tag handling for successful fetches.
- Around line 191-194: Replace the unvalidated JSON shape assertion in the PyPI
response handling with explicit parsing and a guard that verifies releases is a
non-null object before use. Throw a named, descriptive error when validation
fails, then pass the narrowed releases value to the existing Object.keys flow
without using any or unnecessary type assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 20c71361-1bb5-4dd2-852f-c801b50727f3

📥 Commits

Reviewing files that changed from the base of the PR and between bb738b7 and 17ec699.

📒 Files selected for processing (4)
  • .github/workflows/backport-auto-merge.yaml
  • .github/workflows/release-stranded-check.yaml
  • scripts/cicd/check-stranded-release-commits.test.ts
  • scripts/cicd/check-stranded-release-commits.ts

Comment thread .github/workflows/backport-auto-merge.yaml Outdated
Comment thread .github/workflows/backport-auto-merge.yaml Outdated
Comment thread scripts/cicd/check-stranded-release-commits.test.ts Outdated
Comment thread scripts/cicd/check-stranded-release-commits.ts

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/backport-auto-merge.yaml (1)

124-133: ⚠️ Potential issue | 🟠 Major

Update the eligibility guard to use has_supported_label.

Line [124] defines has_supported_label, but Lines [134]-[135] still expand $is_backport. Because this step runs with set -u, an open, non-draft candidate causes an is_backport: unbound variable failure. The job exits before it can merge or skip the PR.

Replace both references and update the log to include Release PRs.

Proposed fix
-            if [ "$state" != "OPEN" ] || [ "$is_draft" != "false" ] || [ "$is_backport" != "true" ]; then
-              echo "Not an actionable backport PR (state=$state draft=$is_draft backport=$is_backport) — skipping."
+            if [ "$state" != "OPEN" ] || [ "$is_draft" != "false" ] || [ "$has_supported_label" != "true" ]; then
+              echo "Not an actionable backport or Release PR (state=$state draft=$is_draft has_supported_label=$has_supported_label) — skipping."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/backport-auto-merge.yaml around lines 124 - 133, Update
the eligibility guard near the open, non-draft PR checks to use the existing
has_supported_label variable instead of both references to is_backport, avoiding
the unset-variable failure under set -u. Also revise the associated log message
to mention both backport and Release-labelled PRs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/workflows/backport-auto-merge.yaml:
- Around line 124-133: Update the eligibility guard near the open, non-draft PR
checks to use the existing has_supported_label variable instead of both
references to is_backport, avoiding the unset-variable failure under set -u.
Also revise the associated log message to mention both backport and
Release-labelled PRs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d9133b47-360f-4879-9849-0841e3c3c3d4

📥 Commits

Reviewing files that changed from the base of the PR and between 17ec699 and 90faf52.

📒 Files selected for processing (3)
  • .github/workflows/backport-auto-merge.yaml
  • scripts/cicd/check-stranded-release-commits.test.ts
  • scripts/cicd/check-stranded-release-commits.ts
💤 Files with no reviewable changes (1)
  • scripts/cicd/check-stranded-release-commits.test.ts

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 4, 2026
@christian-byrne

Copy link
Copy Markdown
Contributor Author

Two notes from landing #14039 (release-workflow hardening) alongside this:

1. The release: published trigger will never fire for automated releases. release-draft-create.yaml creates the release with the repository's GITHUB_TOKEN, and GitHub does not start new workflow runs from GITHUB_TOKEN-driven events (workflow_dispatch and repository_dispatch are the only exceptions). So on the automated path this check runs only on its daily 16:00 UTC cron — a stranded fix is caught within a day, not at publish time. That still meets the stated goal, but the trigger reads as if it covers publish and it does not. Worth either dropping it or annotating it, so nobody later assumes publish-time coverage that is not there.

2. Overlap with #14039. That PR's release-done job carries an inline stranded-commit assertion (git rev-list vTAG..branch --count) for the version just released. This PR is the better implementation — tested, scoped to consumed lines only, with Slack notification. Once both are in, release-done should drop its inline copy and call scripts/cicd/check-stranded-release-commits.ts instead, so there is one implementation of the rule. Filed as a note rather than a change here since #14039 cannot depend on an unmerged script.

@dante01yoon dante01yoon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Found two blocking regressions in the new release automation. Both prevent the intended automatic patch/release-PR flow from functioning.

state=$(echo "$info" | jq -r '.state')
is_draft=$(echo "$info" | jq -r '.isDraft')
is_backport=$(echo "$info" | jq -r '[.labels[].name] | any(. == "backport")')
has_supported_label=$(echo "$info" | jq -r '[.labels[].name] | any(. == "backport" or . == "Release")')

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue: The rename is incomplete: the eligibility guard below still expands $is_backport twice even though this line now defines only has_supported_label. Because this step starts with set -u, every open, non-draft candidate reaches that expansion and exits with is_backport: unbound variable, so neither backport nor Release PRs can be merged. Could we update both guard/log references to has_supported_label?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct, thank you. The guard and the log still expanded $is_backport after the definition was renamed, and with set -u that aborts on the first candidate, so neither backport nor Release PRs would merge. The auto-merge path was dead rather than merely mislabelled.

Worse, I told a previous reviewer this rename included the log line. It did not. Both references now use has_supported_label.

fi

echo "::warning::Fix commits are stranded on ${BRANCH}; dispatching a patch release."
gh workflow run release-biweekly-comfyui.yaml \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue: This dispatch targets release-biweekly-comfyui.yaml, but that workflow no longer exists on the PR head/default branch; the current workflow is release-weekly-comfyui.yaml (with the same release_type and target_branch inputs). gh workflow run resolves workflows on the default branch, so the remediation step will fail instead of cutting the stranded patch. Could we dispatch the current workflow filename?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Also correct. release-biweekly-comfyui.yaml was renamed to release-weekly-comfyui.yaml and gh workflow run resolves against the default branch, so the remediation step would have failed at exactly the moment it was supposed to cut the stranded patch. Now dispatches release-weekly-comfyui.yaml; I checked that release_type and target_branch are unchanged on main.

Related: #14039 still edits release-biweekly-comfyui.yaml and is 166 commits behind main, so it needs the same rename before it can land.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.github/workflows/release-stranded-check.yaml (2)

73-78: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Filter for the core version-bump PR.

version-bump-desktop-ui.yaml can create a Release-labelled PR against any branch, including core/X.Y. This query can therefore skip the patch dispatch for a non-frontend bump. Filter the result by the version-bump-* head branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-stranded-check.yaml around lines 73 - 78, Update
the OPEN_BUMP query in the release-check workflow to filter Release-labelled PRs
by a head branch matching version-bump-*. Keep the existing repository,
open-state, and target-branch filters so only the core version-bump PR
suppresses patch dispatch.

63-84: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Restrict patch dispatch to the pinned release line.

The current first-match selection can dispatch the newest line when the pinned line has a different failure and the newest line has stranded commits. Filter the stranded-commits finding by the pinned branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-stranded-check.yaml around lines 63 - 84, Update
the stranded-commit selection that feeds steps.check and needs_patch_branch so
it filters findings by the pinned release branch before selecting a match.
Ensure the patch-release dispatch in “Cut the patch release for the stranded
line” can only target that pinned line, rather than falling back to the newest
release line.
.github/workflows/backport-auto-merge.yaml (1)

82-89: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Paginate the scheduled PR sweep.

Each gh pr list command returns at most 100 PRs. If either label exceeds 100 open PRs, some eligible PRs are omitted from the sweep. Paginate both queries or detect the limit and fail or warn.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/backport-auto-merge.yaml around lines 82 - 89, Update the
scheduled PR sweep in the numbers collection to paginate both gh pr list
queries, ensuring all open backport and Release PRs are included rather than
only the first 100. Use gh’s supported pagination options or iterate through
result pages, preserving the separate label queries and combined numbers output.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/workflows/backport-auto-merge.yaml:
- Around line 82-89: Update the scheduled PR sweep in the numbers collection to
paginate both gh pr list queries, ensuring all open backport and Release PRs are
included rather than only the first 100. Use gh’s supported pagination options
or iterate through result pages, preserving the separate label queries and
combined numbers output.

In @.github/workflows/release-stranded-check.yaml:
- Around line 73-78: Update the OPEN_BUMP query in the release-check workflow to
filter Release-labelled PRs by a head branch matching version-bump-*. Keep the
existing repository, open-state, and target-branch filters so only the core
version-bump PR suppresses patch dispatch.
- Around line 63-84: Update the stranded-commit selection that feeds steps.check
and needs_patch_branch so it filters findings by the pinned release branch
before selecting a match. Ensure the patch-release dispatch in “Cut the patch
release for the stranded line” can only target that pinned line, rather than
falling back to the newest release line.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a0aa03cf-5ad5-4f0b-8d1c-09961ebcebb8

📥 Commits

Reviewing files that changed from the base of the PR and between 90faf52 and 4eb49e6.

📒 Files selected for processing (2)
  • .github/workflows/backport-auto-merge.yaml
  • .github/workflows/release-stranded-check.yaml

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 4, 2026
dante01yoon
dante01yoon previously approved these changes Aug 4, 2026

@dante01yoon dante01yoon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-checked the focused fixes at 4eb49e6a:

  • The eligibility guard and log now consistently use has_supported_label; a set -u shell probe accepts both supported labels and skips unsupported labels without an unbound-variable failure.
  • Remediation now dispatches release-weekly-comfyui.yaml; that workflow exists on main and still exposes release_type and target_branch.

Required checks are green.

Encodes the 1.47.10 failure: fix commits merged onto a release branch after
its tag, with PyPI and ComfyUI's pin still on the old tag, must fail loudly.
Stubbed so the spec runs and fails; implementation follows.
Connor Byrne and others added 7 commits August 8, 2026 18:10
Checks only the minors users actually consume — the one ComfyUI pins and the
newest on PyPI — so dead and unreleased branches stay quiet. Fix commits past
the branch's latest tag fail the job and post to #frontend-releases.

Verified against live state: exits 1 and names the four stranded core/1.47
fixes, including 9a65619, the dropdown fix five users reported.
execFileSync instead of execSync: git ref names permit ; $() ` and |, so a tag
name reaching a shell is arbitrary code execution in CI.

Slack answers HTTP 200 with {ok:false} on auth and channel errors, so curl -f
alone reported a silent drop as success. Check .ok and bound the request.

Cover parsePinnedVersion and newestStableVersion, and bound the HTTP fetches.

Also close a hole the review did not reach: the guard called the line 'fully
shipped' while ComfyUI still pinned 1.47.10 against a published 1.47.11 — the
exact state where a fix exists and no user has it.
Validate PyPI's payload instead of asserting its shape, so a changed or error
response gives a clear diagnostic rather than an opaque TypeError.

A missing or renamed branch no longer aborts the run before the other consumed
line is evaluated, and main()'s failures set the exit code explicitly rather
than relying on unhandled-rejection behaviour.
Alerting was never the missing piece. Both times this failed the information
existed and nobody acted, so the check now dispatches the patch release for the
line ComfyUI pins, and skips if a bump PR is already open for it.

Version-bump PRs also join backport auto-merge. They are bot-authored and
mechanical, and leaving them for a human to click is what timed out publish-pypi
on the 1.47.10 and 07-27 runs, which silently skipped the ComfyUI pin PR too.

Branch protection still gates both, so this removes the requirement that someone
remembers, not the requirement that someone approves.
The pinned-line branch was compared against ComfyUI's requirements.txt pin
rather than the newest version actually published for that line. A tag that was
never published passed silently, which is precisely the failure this check
exists to catch.

Repeating --label is AND, so the scheduled sweep matched neither backport nor
Release PRs. Query them separately.

Rename is_backport to has_supported_label, and drop a test that duplicated the
assertion above it while claiming a scenario the function cannot observe.
The eligibility guard still expanded $is_backport after the variable was
renamed. Under set -u that aborts on every candidate, so neither backport nor
Release PRs could merge: the auto-merge path was dead, not just wrong.

The remediation step dispatched release-biweekly-comfyui.yaml, which no longer
exists. gh workflow run resolves on the default branch, so the step would fail
rather than cut the stranded patch. Now targets release-weekly-comfyui.yaml,
whose release_type and target_branch inputs are unchanged.
.pinact.yaml allowlists actions/checkout at v7; v6 has to be SHA-pinned.
@christian-byrne

Copy link
Copy Markdown
Contributor Author

Pushed two commits here so this does not sit red:

  • Rebased onto main — the branch was 115 commits behind.
  • actions/checkout@v6@v7.pinact.yaml allowlists checkout at v7, so v6 has to be SHA-pinned or bumped; validate-pins was going to fail on it after the rebase.

Still outstanding, and I did not touch it because it needs #14039 on main first: the release: published trigger cannot fire on the automated path. Releases are created by release-draft-create with the repository's GITHUB_TOKEN, and GitHub does not start workflow runs from GITHUB_TOKEN-driven events (workflow_dispatch and repository_dispatch are the only exceptions). So today this effectively runs on the daily cron only, and the trigger reads as if it covers publish.

The fix that matches what #14039 landed for release-enforce-latest.yaml: add workflow_call: {} here and invoke it as a job from release-draft-create, keeping release: for manual UI publishes. Happy to push that once #14039 merges.

@christian-byrne

Copy link
Copy Markdown
Contributor Author

Note on the red validate-pins: it is not this PR.

pinact flags .github/workflows/ci-tests-e2e.yaml:263, actions/download-artifact@v7, which is unpinned on main right now and untouched by this branch. The three sibling uses in that same file are on @v8, so it looks like one line was missed during a bump.

The check scans every workflow rather than only changed files, so it will stay red on any PR until that line is pinned. Happy to fix it here if you want it unblocked, but it belongs in its own PR since it is unrelated to this change.

Thanks for the checkout bump in bce0b29 — I left it in place.

@christian-byrne
christian-byrne merged commit b7f0356 into main Aug 9, 2026
63 of 64 checks passed
@christian-byrne
christian-byrne deleted the cb__release-stranded-guard branch August 9, 2026 01:40
pull Bot pushed a commit to Penguinjanator/ComfyUI_frontend that referenced this pull request Aug 11, 2026
…omfy-Org#14997)

Bug in the stranded-commits check from Comfy-Org#14429, which is live and
currently misfiring once a day.

## What happens

The idempotency guard asked "is a Release bump PR open against this
branch?". Once that PR merges but the tag is never cut, the answer is
no, so the next scheduled run dispatches another patch release.

Observed on `core/1.48`:

```
2026-08-09 16:10  check finds e136c13 stranded, dispatches a patch
2026-08-09 16:13  Comfy-Org#14973 "1.48.8" opened
2026-08-10 04:58  Comfy-Org#14973 merged; draft_release fails 403, v1.48.8 never created
2026-08-10 16:20  check runs again, sees no open bump PR, dispatches
2026-08-10 16:21  Comfy-Org#14991 "1.48.9" opened
```

`core/1.48` now has `package.json` at 1.48.8 with `v1.48.7` as its
newest tag. Left alone this burns one version per day for as long as the
underlying failure persists.

## Fix

Compare the branch's `package.json` against its newest tag. When they
differ, a bump has already landed unreleased, so the release needs
recovering rather than restarting, and no dispatch is requested.
Stranded commits are still reported and the job still fails, so nothing
goes quiet.

Verified against the live state that caused the loop:

```
[FAIL] 1 fix commit(s) sit past v1.48.7 on core/1.48 and have not shipped:
    e136c13 [backport core/1.48] fix(billing): stop reporting "processing" ...
core/1.48 is at 1.48.8 but its newest tag is v1.48.7: a bump already landed and
was never released. Recover that release rather than cutting another patch; not
requesting a dispatch.
```

`needs_patch_branch` is not emitted, so the dispatch step is skipped.

## Separate, and the actual blocker

`draft_release` fails with `403 Resource not accessible by integration`
when creating the GitHub release. No core release can be tagged until
that is resolved, and this PR does not address it.

Comfy-Org#14991 should probably be closed so the version stops advancing, leaving
`core/1.48` at 1.48.8 ready to tag.

---------

Co-authored-by: Connor Byrne <c.byrne@comfy.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants