Skip to content

Support workflow terminate rollback#14465

Merged
vaishnav-mk merged 10 commits into
mainfrom
vaish/terminate-rollback
Jul 9, 2026
Merged

Support workflow terminate rollback#14465
vaishnav-mk merged 10 commits into
mainfrom
vaish/terminate-rollback

Conversation

@vaishnav-mk

@vaishnav-mk vaishnav-mk commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Adds terminate rollback support across Workflows local tooling and the remote Wrangler terminate command.

This wires rollback: true through the terminate path:

  • @cloudflare/workflows-shared binding and local engine
  • Miniflare wrapped Workflows binding
  • Local Explorer API/OpenAPI schema
  • Wrangler workflows instances terminate --rollback
  • Wrangler workflows instances terminate --local --rollback
  • Local rollback recovery now matches production shape: replay rebuilds rollback handlers from persisted eligible steps, started/failed cached steps are covered, and missing rebuilt handlers fail rollback loudly instead of being silently skipped.

The production Workflows API already accepts terminate rollback; this PR adds the SDK/Wrangler client surfaces and fixes local rollback recovery so rollback can run after local engine restart/eviction.

Rollback is only valid for terminate and is only serialized when explicitly set to true.


A picture of a cute animal (not mandatory, but encouraged)
wires cat
image

@changeset-bot

changeset-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7bb3bc4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 7 packages
Name Type
@cloudflare/workflows-shared Minor
wrangler Minor
miniflare Minor
@cloudflare/vitest-pool-workers Patch
@cloudflare/vite-plugin Patch
@cloudflare/deploy-helpers Patch
@cloudflare/pages-shared Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

✅ All changesets look good

@vaishnav-mk vaishnav-mk force-pushed the vaish/terminate-rollback branch 2 times, most recently from 4ccbf75 to 01fd44d Compare June 29, 2026 06:36
@pkg-pr-new

pkg-pr-new Bot commented Jun 29, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@14465

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@14465

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@14465

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@14465

miniflare

npm i https://pkg.pr.new/miniflare@14465

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@14465

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@14465

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@14465

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@14465

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@14465

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@14465

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@14465

wrangler

npm i https://pkg.pr.new/wrangler@14465

commit: 7bb3bc4

@vaishnav-mk vaishnav-mk force-pushed the vaish/terminate-rollback branch 5 times, most recently from f9b1b1a to 9ed9edf Compare June 29, 2026 06:59
@ask-bonk

ask-bonk Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

@vaishnav-mk Bonk workflow failed. Check the logs for details.

View workflow run · To retry, trigger Bonk again.

@vaishnav-mk vaishnav-mk force-pushed the vaish/terminate-rollback branch 3 times, most recently from 50bf663 to 2c0a39a Compare June 29, 2026 07:09
Comment thread packages/miniflare/src/workers/local-explorer/openapi.local.json Outdated
Comment thread packages/miniflare/src/workers/workflows/wrapped-binding.worker.ts Outdated
Comment thread packages/workflows-shared/src/binding.ts Outdated
@vaishnav-mk vaishnav-mk force-pushed the vaish/terminate-rollback branch 2 times, most recently from 4bafb21 to ef038d9 Compare June 29, 2026 10:23
@vaishnav-mk vaishnav-mk marked this pull request as ready for review June 29, 2026 10:55
@workers-devprod workers-devprod requested review from a team and james-elicx and removed request for a team June 29, 2026 10:55
@workers-devprod

workers-devprod commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
  • ✅ @cloudflare/workflows
Show detailed file reviewers
  • .changeset/workflows-terminate-rollback.md: [@cloudflare/wrangler]
  • packages/miniflare/scripts/openapi-filter-config.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/workers/local-explorer/generated/types.gen.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/workers/local-explorer/generated/zod.gen.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/workers/local-explorer/openapi.local.json: [@cloudflare/wrangler]
  • packages/miniflare/src/workers/local-explorer/resources/workflows.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/workers/workflows/wrapped-binding.worker.ts: [@cloudflare/wrangler]
  • packages/workflows-shared/src/binding.ts: [@cloudflare/wrangler]
  • packages/workflows-shared/src/context.ts: [@cloudflare/wrangler]
  • packages/workflows-shared/src/engine.ts: [@cloudflare/wrangler]
  • packages/workflows-shared/src/lib/rollback.ts: [@cloudflare/wrangler]
  • packages/workflows-shared/tests/engine.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/workflows.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/workflows/commands/instances/terminate.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/workflows/local.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/workflows/utils.ts: [@cloudflare/wrangler]

devin-ai-integration[bot]

This comment was marked as resolved.

@pombosilva pombosilva self-requested a review June 29, 2026 11:29
devin-ai-integration[bot]

This comment was marked as resolved.

@vaishnav-mk

Copy link
Copy Markdown
Contributor Author

/bonk review

@ask-bonk ask-bonk 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.

Review: Support workflow terminate rollback (re-review @ dd02374)

Re-reviewing after the two commits added since the last /bonk review (6c313a6dd02374). Most of the previously-raised concerns are now resolved. Details below.

Previously-raised items — status

1. Remote (production) API path sends rollback: true — now substantiated. ✅ (with one ask)

The prior review flagged utils.ts:131 as blocking because it was unclear whether the production API accepts rollback on terminate. The linked docs PR (cloudflare-docs#31769) documents the remote CLI form as a supported operation:

npx wrangler workflows instances terminate <WORKFLOW_NAME> <INSTANCE_ID> --rollback

and describes production semantics ("Workflows runs the rollback handlers registered by completed or eligible steps before the instance reaches the terminated state. Steps without rollback handlers are skipped."). That's good evidence the production API is ready and this is intentional, so I no longer consider it blocking. One remaining ask: please confirm the production PATCH .../status endpoint accepting rollback is already deployed (not gated behind the docs merge), since terminate.ts:65 prints unconditional success — if the field is silently dropped server-side today, users get a false "terminated successfully" for a rollback that never ran.

2. Devin's "failed steps won't re-register rollback handlers on replay" — resolved. ✅

dd02374 fixes this directly: the maybeError branch in context.ts:461-475 now calls #registerRollback before re-throwing the cached error, and the replay branch at context.ts:484-497 registers rollback and returns early. New test replays cached failed steps before terminate rollback when registry is empty (engine.test.ts) asserts ROLLBACK_STEP_SUCCESS fires for the failed step. Good coverage.

3. Repeated phase string-union nit — resolved. ✅
RollbackPhase type alias is now used across setRollbackPhase / rollbackPhase (engine.ts:336).

4. Double full-table scan in getEligibleRollbackSteps — resolved. ✅
Now a single SELECT ... ORDER BY id DESC pass building the sets in one loop (engine.ts:256-321).

Still open — non-blocking

5. Swallowed errors in replayRollbackRegistry.
engine.ts:988 catches and discards all replay errors with only a comment; engine.ts:1017 logs rollback failures to console.error. For a local-dev tool, "why didn't my rollback run?" is a real question. A debug-level log in the replay catch (even just the error message) would make silent zero-rollback outcomes diagnosable.

6. No user-facing signal for rollback outcome.
terminate.ts:65 prints plain success regardless of whether any rollback steps executed, were skipped, or failed. Not blocking, but surfacing a count (e.g. "ran N rollback step(s)") would make the feature observable — especially relevant given (5) and the empty-registry edge cases.

Minor / nits

  • resources/workflows.ts:924 still uses handle as unknown as WorkflowHandle to pass terminate options, mirroring the same @cloudflare/workers-types gap. Since the docs PR now types terminate(options?: WorkflowInstanceTerminateOptions), worth a single tracking note so this cast is removed once the types package ships.
  • registerRollbackFn (lib/rollback.ts) now updates stepContext/output in place on re-registration instead of disposing — correct for replay, but confirm the previous dupRollbackStub/disposeRollbackStub lifecycle isn't leaking stubs when a cacheKey re-registers with a fresh fn (the update path keeps existing.fn and drops the new registration.fn without disposing it).

Tests / changeset

  • Changeset (minor for workflows-shared/wrangler/miniflare, patches for dependents) looks correct.
  • Wrangler tests assert exact request bodies for both local and remote --rollback — good.

Overall: the design (persisted hasRollback + replay-to-rebuild-registry) is sound and the newest commit closed the last correctness gap Devin flagged. Approving pending confirmation on point (1) — production API rollback support being live.

@ask-bonk

ask-bonk Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #14465. Summary of what I found on re-review (head commit dd02374, which added two commits since the previous /bonk review):

Resolved since last review

  • Remote API concern (previously blocking): the linked docs PR documents the remote --rollback CLI form and production semantics, indicating the production API supports it. Downgraded to a non-blocking confirmation ask (is the endpoint live, given terminate.ts prints unconditional success).
  • Devin's failed-step replay concern: fixed in dd02374context.ts now registers rollback handlers before re-throwing cached errors and in the replay branch, with a new test asserting ROLLBACK_STEP_SUCCESS for failed steps.
  • Phase string-union nit: RollbackPhase type alias now used.
  • Double table-scan nit: now a single SQL pass.

Still open (non-blocking)

  • Swallowed errors in replayRollbackRegistry/userTriggeredTerminate — suggest a debug log.
  • No user-facing signal whether rollbacks ran vs. skipped.

Nits

  • Remaining as unknown as WorkflowHandle cast in resources/workflows.ts:924.
  • registerRollbackFn re-registration path keeps existing.fn and drops the new fn without disposing — worth confirming no stub leak.

Net: sound design, last correctness gap closed; approving pending confirmation on production API readiness.

github run

@vaishnav-mk

Copy link
Copy Markdown
Contributor Author

/bonk rereview

@ask-bonk ask-bonk 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.

Review: Support workflow terminate rollback (re-review @ 7b9fa6a)

Re-reviewing after the one commit added since the last /bonk review (dd023747b9fa6a, "Clarify rollback replay diagnostics"). That commit directly addresses two of the open non-blocking items from last time, so the diff is small and additive. Design remains sound.

Newly resolved since last review

Swallowed replay errors — resolved. ✅
engine.ts:988-991 now captures the replay error and emits console.debug("Rollback replay stopped:", replayErr) instead of silently discarding it. This makes the "why didn't my rollback run?" case diagnosable in local dev, which was my previous item (5). Since workflows-shared is a Worker package (not wrangler), console.debug is the right primitive here — consistent with the existing console.error/console.warn usage in this file.

registerRollbackFn re-registration stub lifecycle — clarified. ✅
lib/rollback.ts:113-114 now documents that the existing entry already owns the duped stub and duplicate registrations refresh stepContext/output only (the helper hasn't duped the incoming fn, so there's nothing to dispose). This resolves the leak concern I raised last time — confirms it's intentional, not an oversight.

Still open — non-blocking

1. Confirm production PATCH .../status accepts rollback and is deployed.
Still my only real ask. terminate.ts:65 prints unconditional 🥷 ... terminated successfully regardless of whether the remote API honored rollback: true (utils.ts:131). I checked the linked docs PR (cloudflare-docs#31769) — it's still in Draft with no approving reviews yet, so it documents intended semantics but doesn't by itself prove the production endpoint is live today. If the field is silently dropped server-side, remote users get a false success for a rollback that never ran. Please confirm the endpoint is deployed (independent of the docs merge); if it isn't, gate --rollback to --local until it is.

2. Rollback outcome still not surfaced to the user.
Nice to see executeRollbacks now returns { ranAny, allSucceeded } (lib/rollback.ts:172,225) — but both call sites (engine.ts:1016 and engine.ts:1302) discard the result, so it never reaches the CLI, and terminate.ts still prints a flat success. The plumbing is now half there; propagating a count (e.g. "ran N rollback step(s)") would close the observability gap and pairs well with item (1). Non-blocking.

Minor / nits (unchanged)

  • resources/workflows.ts:924 still uses handle as unknown as WorkflowHandle for terminate(options), mirroring the @cloudflare/workers-types terminate-options gap (same TODO as the restart cast at :919). Worth a single tracking note so both casts are removed once the types package ships.

Tests / changeset

  • No test/changeset changes in this commit; the prior coverage (live / replayed / paused registry, failed-step re-registration asserting ROLLBACK_STEP_SUCCESS, and exact request-body assertions for local + remote --rollback) still stands.
  • Changeset (minor for workflows-shared/wrangler/miniflare, patches for dependents) looks correct.

Overall: the last two commits closed the correctness gap (failed-step replay) and the diagnosability gap (replay debug log). The design (persisted hasRollback + replay-to-rebuild-registry) is solid. Approving pending confirmation on item (1) — production API rollback support being deployed, given the unconditional success message and the still-draft docs PR.

@ask-bonk

ask-bonk Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Re-review posted to PR #14465.

Summary

I re-reviewed at head commit 7b9fa6a, which added one commit ("Clarify rollback replay diagnostics") since my last review at dd02374. The diff was small and additive (+4/-1 across engine.ts and lib/rollback.ts).

Newly resolved:

  • Swallowed replay errorsengine.ts:988-991 now emits console.debug("Rollback replay stopped:", replayErr) instead of silently discarding, making empty-rollback outcomes diagnosable.
  • registerRollbackFn stub lifecycle nit — a clarifying comment at lib/rollback.ts:113-114 confirms the existing entry owns the duped stub and re-registration intentionally refreshes only context/output (no leak).

Still open (non-blocking):

  1. Production API readiness — my one remaining ask. terminate.ts:65 prints unconditional success while utils.ts:131 sends rollback: true to the remote endpoint. I verified the linked docs PR (cloudflare-docs#31769) is still in Draft with no approvals, so it doesn't prove the production endpoint is deployed. Asked to confirm the endpoint is live or gate --rollback to --local.
  2. Rollback outcome not surfacedexecuteRollbacks now returns {ranAny, allSucceeded} but both call sites discard it, so the CLI still prints flat success. Plumbing is half there; propagating a count would close the gap.

Minor: the remaining as unknown as WorkflowHandle cast at resources/workflows.ts:924 (types-package gap).

Net: last two commits closed the failed-step replay correctness gap and the diagnosability gap. Design is sound; approving pending confirmation on the production API being deployed.

github run

@devin-ai-integration devin-ai-integration 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.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment thread packages/workflows-shared/src/engine.ts
@vaishnav-mk vaishnav-mk force-pushed the vaish/terminate-rollback branch from ce29ed6 to 7bb3bc4 Compare July 3, 2026 07:47
@vaishnav-mk

Copy link
Copy Markdown
Contributor Author

/bonk rereview

@ask-bonk

ask-bonk Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

UnknownError: ProviderInitError

github run

@vaishnav-mk

Copy link
Copy Markdown
Contributor Author

/bonk rereview

@ask-bonk ask-bonk 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.

Review: Support workflow terminate rollback (re-review @ 7bb3bc4)

Re-reviewing after the two commits added since the last /bonk rereview (7b9fa6a7bb3bc4: "Cover rollback replay for unfinished steps" and "Fail rollback when replay misses eligible step"). These directly address Devin's most recent non-determinism concern, and the design is now noticeably more robust. Details below.

Newly resolved since last review

Devin's "replay could register handlers for the wrong steps / miss steps" concern — resolved. ✅
7bb3bc4 re-architects executeRollbacks (lib/rollback.ts:141-211) so execution is now driven by the persisted eligible steps (readEligibleRollbackStepsDesc()), not by whatever the in-memory rollbackRegistry happens to contain. The registry is now only the source of the rollback function; storage is the source of truth for which steps must roll back. Concretely:

  • If a persisted eligible step has no handler in the registry after replay (e.g. non-deterministic control flow that didn't re-reach it), it now writes ROLLBACK_STEP_FAILURE + ROLLBACK_FAILED and halts, instead of silently skipping (lib/rollback.ts:162-176). That's the correct "production treats this as rollback failure" behavior.
  • Eligibility is computed as a STEP_START whose group has hasRollback: true and no prior ROLLBACK_STEP_SUCCESS/FAILURE (engine.ts:246-315) — so already-rolled-back steps aren't re-run, and both the live and post-restart paths now agree on the same set. Good unification.
  • New test fails terminate rollback when replay cannot rebuild an eligible handler asserts exactly ROLLBACK_STEP_FAILURE=["branchy-setup-1"], ROLLBACK_FAILED=1, ROLLBACK_COMPLETE=0. Solid coverage of the failure path.

Unfinished-step replay + latent priorityQueue crash — resolved. ✅
4ecfb4f adds coverage for started-but-unfinished steps (replays started unfinished steps before terminate rollback..., asserting ROLLBACK_STEP_SUCCESS=["started-setup-1"]). 7bb3bc4 pairs this with engine.ts:1010: this.priorityQueue ??= new TimePriorityQueue(...) before replayRollbackRegistry. This matters — during a post-restart terminate, init() hasn't run, so priorityQueue is undefined; replaying an unfinished step reaches context.ts:521-524 etc. which access this.#engine.priorityQueue under @ts-expect-error priorityQueue is initiated in init. Without this defensive init that path would throw at runtime. Good catch.

Still open — non-blocking (unchanged)

1. Confirm production PATCH .../status accepts rollback and is deployed.
Still my one real ask. terminate.ts:65 prints unconditional 🥷 ... terminated successfully regardless of whether the remote API honored rollback: true (utils.ts:131). The linked docs PR (cloudflare-docs#31769) documents intended semantics but doesn't prove the endpoint is live. If the field is silently dropped server-side, remote users get a false success for a rollback that never ran. Please confirm the endpoint is deployed; if not, gate --rollback to --local until it is.

2. Rollback outcome still not surfaced to the CLI.
executeRollbacks returns { ranAny, allSucceeded }, but both call sites (engine.ts:1017, engine.ts:1303) still discard it, and terminate.ts prints flat success. Now that a failed/missing rollback produces ROLLBACK_FAILED, surfacing a count (e.g. "ran N rollback step(s)") would make the difference between "ran", "skipped", and "failed" observable to the user. Pairs naturally with (1).

Minor / nits (unchanged)

  • resources/workflows.ts:924 still uses handle as unknown as WorkflowHandle for terminate(options) — same @cloudflare/workers-types terminate-options gap as the restart cast. Worth a tracking note so both are removed once the types package ships.

Tests / changeset

  • New tests (started-unfinished replay, missing-handler failure) join the existing live / replayed / paused-registry and cached-failed-step coverage. Request-body assertions for local + remote --rollback still stand.
  • Changeset (minor for workflows-shared/wrangler/miniflare, patches for dependents) looks correct.

Overall: the two new commits close the last correctness gap — rollback is now anchored to persisted eligible steps, missing handlers fail loudly instead of silently, and the replay path no longer risks an undefined-priorityQueue crash. Design (persisted hasRollback + replay-to-rebuild + storage-driven execution) is sound. Approving pending confirmation on item (1) — production API rollback support being deployed, given the unconditional success message.

github run

@ask-bonk

ask-bonk Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Re-review posted to PR #14465.

Summary

I re-reviewed at head commit 7bb3bc4, which added two commits since my last review at 7b9fa6a: "Cover rollback replay for unfinished steps" (4ecfb4f) and "Fail rollback when replay misses eligible step" (7bb3bc4).

Newly resolved:

  • Devin's replay non-determinism / missing-handler concernexecuteRollbacks is re-architected so execution is now driven by persisted eligible steps (readEligibleRollbackStepsDesc) rather than the volatile in-memory registry. A persisted eligible step whose handler isn't rebuilt during replay now writes ROLLBACK_STEP_FAILURE + ROLLBACK_FAILED and halts instead of silently skipping. New test asserts this exactly.
  • Unfinished-step replay + latent crashengine.ts:1010 now does priorityQueue ??= new TimePriorityQueue(...) before replay, preventing an undefined-priorityQueue crash when terminate runs post-restart before init(). New test covers started-but-unfinished step rollback.

Still open (non-blocking):

  1. Production PATCH .../status rollback support — terminate.ts:65 prints unconditional success while the remote path sends rollback: true. Confirm the endpoint is deployed or gate --rollback to --local.
  2. executeRollbacks returns { ranAny, allSucceeded } but both call sites still discard it — CLI prints flat success.

Minor: remaining as unknown as WorkflowHandle cast at resources/workflows.ts:924.

Net: the design is sound and the last correctness gap is closed. Approving pending confirmation on item (1).

I could not run the test suite (dependencies aren't installed in this environment), so my assessment is based on static analysis of the diff and surrounding code.

github run

@vaishnav-mk

Copy link
Copy Markdown
Contributor Author

prod api already supports this. config-service accepts rollback on terminate, engine handles terminate({ rollback }). no --local gate needed.

outcome surfacing is out of scope here. prod terminate response is only { status, timestamp }; rollback result is visible later via status/logs.

cast is temporary until workers-types ships terminate(options). kept local with todo.

Review: Support workflow terminate rollback (re-review @ 7bb3bc4)

Re-reviewing after the two commits added since the last /bonk rereview (7b9fa6a7bb3bc4: "Cover rollback replay for unfinished steps" and "Fail rollback when replay misses eligible step"). These directly address Devin's most recent non-determinism concern, and the design is now noticeably more robust. Details below.

Newly resolved since last review

Devin's "replay could register handlers for the wrong steps / miss steps" concern — resolved. ✅ 7bb3bc4 re-architects executeRollbacks (lib/rollback.ts:141-211) so execution is now driven by the persisted eligible steps (readEligibleRollbackStepsDesc()), not by whatever the in-memory rollbackRegistry happens to contain. The registry is now only the source of the rollback function; storage is the source of truth for which steps must roll back. Concretely:

  • If a persisted eligible step has no handler in the registry after replay (e.g. non-deterministic control flow that didn't re-reach it), it now writes ROLLBACK_STEP_FAILURE + ROLLBACK_FAILED and halts, instead of silently skipping (lib/rollback.ts:162-176). That's the correct "production treats this as rollback failure" behavior.
  • Eligibility is computed as a STEP_START whose group has hasRollback: true and no prior ROLLBACK_STEP_SUCCESS/FAILURE (engine.ts:246-315) — so already-rolled-back steps aren't re-run, and both the live and post-restart paths now agree on the same set. Good unification.
  • New test fails terminate rollback when replay cannot rebuild an eligible handler asserts exactly ROLLBACK_STEP_FAILURE=["branchy-setup-1"], ROLLBACK_FAILED=1, ROLLBACK_COMPLETE=0. Solid coverage of the failure path.

Unfinished-step replay + latent priorityQueue crash — resolved. ✅ 4ecfb4f adds coverage for started-but-unfinished steps (replays started unfinished steps before terminate rollback..., asserting ROLLBACK_STEP_SUCCESS=["started-setup-1"]). 7bb3bc4 pairs this with engine.ts:1010: this.priorityQueue ??= new TimePriorityQueue(...) before replayRollbackRegistry. This matters — during a post-restart terminate, init() hasn't run, so priorityQueue is undefined; replaying an unfinished step reaches context.ts:521-524 etc. which access this.#engine.priorityQueue under @ts-expect-error priorityQueue is initiated in init. Without this defensive init that path would throw at runtime. Good catch.

Still open — non-blocking (unchanged)

1. Confirm production PATCH .../status accepts rollback and is deployed. Still my one real ask. terminate.ts:65 prints unconditional 🥷 ... terminated successfully regardless of whether the remote API honored rollback: true (utils.ts:131). The linked docs PR (cloudflare-docs#31769) documents intended semantics but doesn't prove the endpoint is live. If the field is silently dropped server-side, remote users get a false success for a rollback that never ran. Please confirm the endpoint is deployed; if not, gate --rollback to --local until it is.

2. Rollback outcome still not surfaced to the CLI. executeRollbacks returns { ranAny, allSucceeded }, but both call sites (engine.ts:1017, engine.ts:1303) still discard it, and terminate.ts prints flat success. Now that a failed/missing rollback produces ROLLBACK_FAILED, surfacing a count (e.g. "ran N rollback step(s)") would make the difference between "ran", "skipped", and "failed" observable to the user. Pairs naturally with (1).

Minor / nits (unchanged)

  • resources/workflows.ts:924 still uses handle as unknown as WorkflowHandle for terminate(options) — same @cloudflare/workers-types terminate-options gap as the restart cast. Worth a tracking note so both are removed once the types package ships.

Tests / changeset

  • New tests (started-unfinished replay, missing-handler failure) join the existing live / replayed / paused-registry and cached-failed-step coverage. Request-body assertions for local + remote --rollback still stand.
  • Changeset (minor for workflows-shared/wrangler/miniflare, patches for dependents) looks correct.

Overall: the two new commits close the last correctness gap — rollback is now anchored to persisted eligible steps, missing handlers fail loudly instead of silently, and the replay path no longer risks an undefined-priorityQueue crash. Design (persisted hasRollback + replay-to-rebuild + storage-driven execution) is sound. Approving pending confirmation on item (1) — production API rollback support being deployed, given the unconditional success message.

github run

@ask-bonk ask-bonk 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.

Thanks for the confirmations, @vaishnav-mk — that resolves my open items. Recording where each lands:

1. Production API rollback support — resolved. ✅ You've confirmed config-service accepts rollback on terminate and engine handles terminate({ rollback }) server-side. That's exactly the deployment signal I was asking for; with the production endpoint honoring the field, the unconditional 🥷 ... terminated successfully at terminate.ts:65 for the remote path is no longer a false-success risk, and no --local gate on --rollback is needed. The remote request-body assertion in workflows.test.ts is correct to keep.

2. Outcome surfacing — acknowledged as out of scope. ✅ Agreed this is a reasonable line to draw given the production terminate response is only { status, timestamp } and the rollback result surfaces later via status/logs. executeRollbacks still returns { ranAny, allSucceeded } and both call sites (engine.ts) discard it — leaving the plumbing in place for a future "ran N rollback step(s)" enhancement is fine; not blocking.

3. as unknown as WorkflowHandle cast — acknowledged. ✅ Confirmed it's kept local to terminate() with a TODO (resources/workflows.ts:923, wrapped-binding.worker.ts:112), mirroring the existing restart cast. Tracking note is sufficient; both come out together once @cloudflare/workers-types ships terminate/restart options.

All three prior concerns are now either resolved or accepted as scoped-out with sound rationale. The design — persisted hasRollback + replay-to-rebuild + storage-driven eligible-step execution with loud failure on missing handlers — is solid, and the test coverage (live / replayed / paused-registry / cached-failed-step / started-unfinished replay / missing-handler failure) is thorough.

Approving. 🚀

@ask-bonk

ask-bonk Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Done. I verified @vaishnav-mk's three responses against the actual code and posted an approving review to PR #14465.

What I verified:

  • utils.ts:131 sends rollback: true to the remote PATCH .../status endpoint — author confirmed config-service accepts it and the engine handles terminate({ rollback }) server-side, resolving my one real ask (production API readiness). No --local gate needed.
  • The as unknown as WorkflowHandle cast at resources/workflows.ts:923 and wrapped-binding.worker.ts:112 is kept local to terminate() with a TODO, mirroring the existing restart cast — acknowledged as sufficient.
  • executeRollbacks still returns { ranAny, allSucceeded } while call sites discard it — accepted as out of scope since the prod terminate response is only { status, timestamp } and rollback outcome surfaces later via status/logs.

All prior open items are now resolved or scoped-out with sound rationale, so I approved the PR.

github run

@workers-devprod

workers-devprod commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • ✅ @cloudflare/workflows
  • ✅ @cloudflare/wrangler
Show detailed file reviewers

@workers-devprod workers-devprod 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.

Codeowners reviews satisfied

@github-project-automation github-project-automation Bot moved this from Untriaged to Approved in workers-sdk Jul 6, 2026
@vaishnav-mk vaishnav-mk enabled auto-merge (squash) July 6, 2026 11:39
@vaishnav-mk vaishnav-mk merged commit 2fedb1f into main Jul 9, 2026
73 of 74 checks passed
@vaishnav-mk vaishnav-mk deleted the vaish/terminate-rollback branch July 9, 2026 13:17
@github-project-automation github-project-automation Bot moved this from Approved to Done in workers-sdk Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants