[backport cloud/1.47] fix(workspace): derive Members access from Team plan#13814
[backport cloud/1.47] fix(workspace): derive Members access from Team plan#13814comfy-pr-bot wants to merge 2 commits into
Conversation
## Summary Make Members eligibility follow the active billing plan instead of the personal/team workspace type. A personal workspace on a Team plan now gets the same Members experience as a team workspace on the same plan. ## Root cause The Members surface had three independent workspace-type gates: - `useTeamPlan` treated “team workspace + subscribed” as the Team-plan signal, so a personal workspace was always classified as not being on Team even when billing reported a Team plan. - `useMembersPanel` inherited the personal-workspace permission and layout defaults, which forced the single-user row, disabled Invite, and rendered the Team upgrade banner. - Member and pending-invite loading short-circuited for personal workspaces, so valid Team-plan members could not be fetched even if the UI gate was corrected. ## How this fixes it - Derive `hasTeamPlan`, active Team-plan, cancelled/lapsed, and loading states from billing plan identity plus subscription state. - Overlay Members permissions and table layout from the plan and workspace role. Team-plan owners can invite and manage members regardless of workspace type; personal plans keep the single-user experience. - Wait for billing initialization before deciding whether to fetch members/invites or show an upgrade state. - Remove the store-level personal-workspace short-circuits for member and invite APIs. - Keep workspace-container actions such as delete/leave governed by workspace type; only the Members entitlement moves to plan-based logic. ## Before / After ### Before A personal workspace with a Team plan was treated as a personal plan: Invite was disabled and the UI asked the user to upgrade to Team. <img width="1400" alt="Before: Personal Workspace on Team plan incorrectly shows disabled Invite and Upgrade to Team" src="https://github.com/user-attachments/assets/f0d7a99c-6d7b-4cac-b3c0-9d3c9266830f" /> ### After The same personal-workspace + Team-plan scenario loads the Team member list and enables owner member-management actions. <img width="1400" alt="After: Personal Workspace on Team plan shows member list, Invite, roles, and member actions" src="https://github.com/user-attachments/assets/7e2fbcd7-d8e3-4ae8-a9df-ad59974ecf52" /> ## Review Focus - Team-plan owners receive member management regardless of workspace type. - Personal plans remain single-user. - Loading, cancelled, and lapsed states do not briefly expose an incorrect upgrade or invite state. - Workspace delete/leave behavior remains workspace-type based. ## Validation - 168 focused Vitest tests passed. - `pnpm typecheck` passed. - `pnpm lint` passed. - `pnpm knip` passed. - Cloud Playwright: 7/7 tests passed in `memberRoleChange.spec.ts`, including personal workspace + Team plan regression coverage. - Pre-commit style, format, lint, and type checks passed.
🎨 Storybook: ✅ Built — View Storybook📦 Bundle Size
⚡ Performance Report
Absolute values
Raw data{
"timestamp": "2026-07-19T04:11:43.918Z",
"gitSha": "7f942e067cb8f1ccb81cd6be2c0177b0166a0d9a",
"branch": "backport-13810-to-cloud-1.47",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2032.0689999999786,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.492999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 387.246,
"heapDeltaBytes": -4827732,
"heapUsedBytes": 49329368,
"domNodes": 18,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 18.700999999999997,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 2072.6789999999937,
"styleRecalcs": 11,
"styleRecalcDurationMs": 9.421,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 447.729,
"heapDeltaBytes": -7506604,
"heapUsedBytes": 62523296,
"domNodes": -271,
"jsHeapTotalBytes": 19546112,
"scriptDurationMs": 20.354000000000003,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1885.671000000002,
"styleRecalcs": 76,
"styleRecalcDurationMs": 41.077000000000005,
"layouts": 12,
"layoutDurationMs": 3.724,
"taskDurationMs": 814.307,
"heapDeltaBytes": -2300496,
"heapUsedBytes": 50161740,
"domNodes": -258,
"jsHeapTotalBytes": 16904192,
"scriptDurationMs": 112.22,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1841.875000000016,
"styleRecalcs": 75,
"styleRecalcDurationMs": 40.826,
"layouts": 12,
"layoutDurationMs": 3.577,
"taskDurationMs": 843.1759999999999,
"heapDeltaBytes": -12787012,
"heapUsedBytes": 57205860,
"domNodes": -238,
"jsHeapTotalBytes": 19808256,
"scriptDurationMs": 115.60999999999999,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1743.565999999987,
"styleRecalcs": 32,
"styleRecalcDurationMs": 18.477,
"layouts": 6,
"layoutDurationMs": 0.5840000000000001,
"taskDurationMs": 379.524,
"heapDeltaBytes": -3547784,
"heapUsedBytes": 66384892,
"domNodes": -218,
"jsHeapTotalBytes": 12730368,
"scriptDurationMs": 21.706,
"eventListeners": -182,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1742.1269999999822,
"styleRecalcs": 31,
"styleRecalcDurationMs": 21.566000000000003,
"layouts": 6,
"layoutDurationMs": 0.704,
"taskDurationMs": 370.995,
"heapDeltaBytes": -1515552,
"heapUsedBytes": 50017216,
"domNodes": -237,
"jsHeapTotalBytes": 8515584,
"scriptDurationMs": 21.397,
"eventListeners": -186,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 562.8059999999948,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.004000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 344.043,
"heapDeltaBytes": 7496360,
"heapUsedBytes": 66631116,
"domNodes": 18,
"jsHeapTotalBytes": 17301504,
"scriptDurationMs": 56.894999999999996,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666682,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 534.148000000016,
"styleRecalcs": 11,
"styleRecalcDurationMs": 6.989999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 332.525,
"heapDeltaBytes": 7167500,
"heapUsedBytes": 66141664,
"domNodes": 18,
"jsHeapTotalBytes": 19136512,
"scriptDurationMs": 52.181,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2040.8029999999826,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.979000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 519.048,
"heapDeltaBytes": -8762840,
"heapUsedBytes": 64238688,
"domNodes": 20,
"jsHeapTotalBytes": 10104832,
"scriptDurationMs": 86.021,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2040.4760000000124,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.9959999999999996,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 543.264,
"heapDeltaBytes": -8224788,
"heapUsedBytes": 60246856,
"domNodes": -261,
"jsHeapTotalBytes": -1323008,
"scriptDurationMs": 87.663,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2086.7790000000355,
"styleRecalcs": 69,
"styleRecalcDurationMs": 18.157,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1037.072,
"heapDeltaBytes": 29664652,
"heapUsedBytes": 82389076,
"domNodes": -270,
"jsHeapTotalBytes": 204800,
"scriptDurationMs": 369.846,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2129.304999999988,
"styleRecalcs": 70,
"styleRecalcDurationMs": 26.398999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1105.46,
"heapDeltaBytes": 10433716,
"heapUsedBytes": 83962424,
"domNodes": 18,
"jsHeapTotalBytes": 12640256,
"scriptDurationMs": 381.195,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-zoom",
"durationMs": 3136.090999999965,
"styleRecalcs": 63,
"styleRecalcDurationMs": 17.541,
"layouts": 60,
"layoutDurationMs": 7.792000000000001,
"taskDurationMs": 1288.2849999999999,
"heapDeltaBytes": 13387940,
"heapUsedBytes": 69202500,
"domNodes": 8,
"jsHeapTotalBytes": 1835008,
"scriptDurationMs": 460.93600000000004,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-zoom",
"durationMs": 3117.838000000006,
"styleRecalcs": 63,
"styleRecalcDurationMs": 17.309,
"layouts": 60,
"layoutDurationMs": 7.469,
"taskDurationMs": 1278.8619999999999,
"heapDeltaBytes": 13970856,
"heapUsedBytes": 68698204,
"domNodes": 8,
"jsHeapTotalBytes": 0,
"scriptDurationMs": 450.621,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2028.0260000000112,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.2669999999999995,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 565.879,
"heapDeltaBytes": -17190004,
"heapUsedBytes": 59487472,
"domNodes": -267,
"jsHeapTotalBytes": 4530176,
"scriptDurationMs": 89.135,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "minimap-idle",
"durationMs": 2038.626000000022,
"styleRecalcs": 7,
"styleRecalcDurationMs": 8.346,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 597.27,
"heapDeltaBytes": -16703724,
"heapUsedBytes": 59785548,
"domNodes": -271,
"jsHeapTotalBytes": 4005888,
"scriptDurationMs": 91.859,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 614.9800000000027,
"styleRecalcs": 48,
"styleRecalcDurationMs": 16.389,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 420.83900000000006,
"heapDeltaBytes": -22426420,
"heapUsedBytes": 47554292,
"domNodes": -269,
"jsHeapTotalBytes": 5914624,
"scriptDurationMs": 118.765,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 582.3489999999083,
"styleRecalcs": 46,
"styleRecalcDurationMs": 11.141000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 386.552,
"heapDeltaBytes": -1453920,
"heapUsedBytes": 51100600,
"domNodes": -259,
"jsHeapTotalBytes": -135168,
"scriptDurationMs": 114.15499999999999,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2007.2979999999916,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.812000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 384.926,
"heapDeltaBytes": -3609124,
"heapUsedBytes": 49014404,
"domNodes": -284,
"jsHeapTotalBytes": 17428480,
"scriptDurationMs": 14.519999999999998,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-idle",
"durationMs": 2010.5179999999905,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.884999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 403.20099999999996,
"heapDeltaBytes": -2621260,
"heapUsedBytes": 49871472,
"domNodes": -284,
"jsHeapTotalBytes": 17690624,
"scriptDurationMs": 16.129,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1762.1819999999957,
"styleRecalcs": 75,
"styleRecalcDurationMs": 36.255,
"layouts": 16,
"layoutDurationMs": 4.592,
"taskDurationMs": 747.938,
"heapDeltaBytes": 20976708,
"heapUsedBytes": 80208156,
"domNodes": 61,
"jsHeapTotalBytes": 40632320,
"scriptDurationMs": 90.64500000000001,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1746.9950000000267,
"styleRecalcs": 76,
"styleRecalcDurationMs": 42.745,
"layouts": 16,
"layoutDurationMs": 4.803,
"taskDurationMs": 772.946,
"heapDeltaBytes": 16903172,
"heapUsedBytes": 69427464,
"domNodes": -266,
"jsHeapTotalBytes": 14282752,
"scriptDurationMs": 90.837,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-transition-enter",
"durationMs": 948.5459999999648,
"styleRecalcs": 18,
"styleRecalcDurationMs": 29.22099999999999,
"layouts": 14,
"layoutDurationMs": 13.366000000000003,
"taskDurationMs": 751.901,
"heapDeltaBytes": 29863164,
"heapUsedBytes": 97959048,
"domNodes": 13673,
"jsHeapTotalBytes": 14680064,
"scriptDurationMs": 28.699,
"eventListeners": 2531,
"totalBlockingTimeMs": 143,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "viewport-pan-sweep",
"durationMs": 8092.052000000023,
"styleRecalcs": 251,
"styleRecalcDurationMs": 54.894,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3695.046,
"heapDeltaBytes": 16165228,
"heapUsedBytes": 69252976,
"domNodes": -260,
"jsHeapTotalBytes": 2039808,
"scriptDurationMs": 1189.181,
"eventListeners": -183,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8166.804999999954,
"styleRecalcs": 250,
"styleRecalcDurationMs": 55.23400000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3947.8590000000004,
"heapDeltaBytes": 5628196,
"heapUsedBytes": 61711372,
"domNodes": -142,
"jsHeapTotalBytes": 11169792,
"scriptDurationMs": 1236.18,
"eventListeners": -96,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 10632.642000000033,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 10615.659000000001,
"heapDeltaBytes": -52462180,
"heapUsedBytes": 171067148,
"domNodes": -3300,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 526.366,
"eventListeners": -16368,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333338,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 10771.485999999983,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 10752.863000000001,
"heapDeltaBytes": -48662920,
"heapUsedBytes": 167317452,
"domNodes": -3300,
"jsHeapTotalBytes": -6844416,
"scriptDurationMs": 511.8020000000001,
"eventListeners": -16380,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.77333333333336,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 14019.496000000003,
"styleRecalcs": 67,
"styleRecalcDurationMs": 18.68700000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 13999.364,
"heapDeltaBytes": -17766780,
"heapUsedBytes": 177477972,
"domNodes": -3302,
"jsHeapTotalBytes": 16138240,
"scriptDurationMs": 776.883,
"eventListeners": -16370,
"totalBlockingTimeMs": 2,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-pan",
"durationMs": 13402.478000000086,
"styleRecalcs": 65,
"styleRecalcDurationMs": 21.976000000000024,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 13381.004999999997,
"heapDeltaBytes": -46431980,
"heapUsedBytes": 167141248,
"domNodes": -3300,
"jsHeapTotalBytes": 4952064,
"scriptDurationMs": 857.7470000000001,
"eventListeners": -16372,
"totalBlockingTimeMs": 40,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "workflow-execution",
"durationMs": 447.8149999999914,
"styleRecalcs": 15,
"styleRecalcDurationMs": 24.747,
"layouts": 3,
"layoutDurationMs": 1.161,
"taskDurationMs": 198.248,
"heapDeltaBytes": -22904296,
"heapUsedBytes": 46199820,
"domNodes": -146,
"jsHeapTotalBytes": 6701056,
"scriptDurationMs": 10.538,
"eventListeners": -158,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "workflow-execution",
"durationMs": 453.6950000000388,
"styleRecalcs": 17,
"styleRecalcDurationMs": 19.492,
"layouts": 3,
"layoutDurationMs": 1.147,
"taskDurationMs": 100.469,
"heapDeltaBytes": 4775648,
"heapUsedBytes": 56930296,
"domNodes": 141,
"jsHeapTotalBytes": 262144,
"scriptDurationMs": 8.665,
"eventListeners": 65,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
}
]
}🎭 Playwright: ✅ 1690 passed, 0 failed · 5 flaky📊 Browser Reports
|
🎭 Playwright: ⏳ Running... |
dante01yoon
left a comment
There was a problem hiding this comment.
Verified as a clean backport of #13810 to cloud/1.47. The actual added and removed lines match the original PR, the branch is mergeable, and there are no failing checks; remaining CI is still in progress.
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## cloud/1.47 #13814 +/- ##
=============================================
Coverage ? 78.26%
=============================================
Files ? 1662
Lines ? 93135
Branches ? 32251
=============================================
Hits ? 72888
Misses ? 19635
Partials ? 612
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…13834) ## Summary Backports #13812 to `cloud/1.47` after the [automatic cherry-pick](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/29704878740) conflicted. This branch is stacked on the current #13814 head. ## Changes - **What**: Applies role-gated billing mutations, Personal/default creator protection, additional-workspace creator actions, and execution-time permission rechecks. - **Dependencies**: Depends on #13814; merge it first so this PR narrows to the #13812 backport. ## Review Focus - Combines #13814 plan-driven Members access with #13812 role and creator policy. - Corrects the cloud workspace fixture to use `consolidated_billing_enabled` for Personal billing routing while leaving `billing_control_enabled` independent. - Keeps the `BillingStatusBanner` story/test and Storybook workspace mock deleted because their runtime surface is absent on this branch. ## Validation - Focused unit suite: 18 files, 397 tests passed. - Main and browser typechecks passed; focused formatting passed. - Cloud `memberRoleChange.spec.ts`: 7 passed. ## Screenshots | Personal/default workspace creator | Additional workspace creator | | --- | --- | | <img width="720" alt="Personal workspace creator menu with billing actions but no Leave or Delete action" src="https://github.com/user-attachments/assets/1fa2e62f-9b93-4d8c-b9bb-d02a2a671531" /> | <img width="720" alt="Additional workspace creator menu with Leave and subscription-locked Delete actions" src="https://github.com/user-attachments/assets/3cb7255d-2e41-44d4-a7f8-feddcb90644d" /> | <img width="720" alt="Another owner can change the additional workspace creator role or remove the creator" src="https://github.com/user-attachments/assets/8fb7b5a5-63b4-4124-a99a-85121f8fa374" />
Backport of #13810 to
cloud/1.47Automatically created by backport workflow.