Osy#betaRun it on Osyrin: osyrin.com

Reference / Project

Stopping runs after a bad deploy

osy cancel-runs --version <version>

A workflow run keeps executing the version it started under, which is exactly what you want until that version is the problem. cancel-runs stops every run still executing a given version, without running their compensation logic.

stableprojectdeployworkflowoperations

Summary#

A run in flight goes on executing the version it started under — deliberately, and permanently. That is the guarantee that makes deploying safe while work is in progress.

It is also a trap the first time you ship something broken. You notice, you deploy the fix, and the fix only applies to new runs: every run already started keeps executing the broken code to completion. cancel-runs is how you stop them.

Signature#

$ osy cancel-runs --version <version>           # local
$ osyrin app cancel-runs <version>              # a deployed app

Description#

What it does#

Every workflow run still in flight against the named version is stopped: marked cancelled, recorded in the run's audit timeline as such, and any parked continuation discarded so nothing can wake it again. Runs on other versions are untouched.

Find the version with Deploying while workflows are running — the In flight column is what you are about to cancel:

$ osy versions
$ osy cancel-runs --version 1.1.0

It tells you how many runs you are about to stop and asks before doing it. --yes skips the prompt.

It stops whole sagas, not single runs#

A saga is a tree: a parent waiting on a child, which may itself be waiting on another. Cancelling one run of that tree cancels all of it, whichever run you name.

That is not overreach, it is the only coherent option. Cancelling just the child would leave the parent waiting forever on something that can never finish — one stuck run traded for another. Cancelling just the parent would leave children doing work whose result nothing will ever read.

Stopping one run#

Sometimes it is not a version that is wrong, only one run. The usual case is a run whose start kept failing. Admin's Runs page puts every run that recorded an error and is still in flight on its own line, with the error under it and a Cancel run button. Cancelling asks you first, then stops that run and its saga tree, exactly as above. Nothing else of that version is touched. On an app a zone serves, the grant covers that one run only.

You rarely need it for a start that failed for good. When the platform gives up retrying a run's start, it ends the run Failed by itself, with the run's own error on it. A schedule with Overlap = Skip then fires again at its next time, rather than waiting for ever on a run that would never finish.

It does not run your compensation logic#

This is the important one, and it is deliberate.

When a saga fails normally, its catch runs and compensates — undoing what it did. Cancelling does not do that. The reason you are reaching for cancel-runs is usually that the deployed code is wrong, and your compensation logic is that same deployed code. Running it would execute the very thing you are trying to escape, and on a saga that is already in a state you did not intend.

So: side effects those runs already performed remain performed. Money moved stays moved, emails sent stay sent. The audit says the run was cancelled, not that it was compensated, so the record does not claim an unwind that never happened. Undoing that work is yours to do deliberately — with full knowledge of what actually ran.

Cancelling is a blunt instrument on purpose. It is the tool for "stop, this is wrong", not for "unwind this cleanly".

It also lets the version be cleaned up#

Runs in flight are exactly what stops an old version being reclaimed. Once you have cancelled them, nothing holds that version open and the next deploy reclaims it — so the broken version stops appearing in versions too.

Who can run it#

The same permission as deploying to the app. Whoever can ship the bad version can stop it.

On an app a zone serves#

A deployed app usually runs in a zone, not on the control plane you signed in to. osyrin app versions and osyrin app cancel-runs work the same way there: the CLI asks the control plane for a short grant for this one operation, then sends the request to the zone that serves the app. A cancel-runs grant covers only the version you named, and every grant expires after five minutes. Your sign-in never reaches the zone. You do not need to do anything different. If the CLI says the app is served by a zone, and a zone does not take your platform sign-in, the control plane is older than this route. Logging in again will not change that.

Examples#

The whole sequence, from noticing to clean:

# 1. What is running, and against which version?
osy versions
# → 1.2.0  app_v3  14 in flight  oldest 09:12 (2h ago)  held by runs in flight
#   1.3.0  app_v4   0            —                      current

# 2. Deploy the fix. New runs are fine from here; the 14 are not.
osy compile --new-version

# 3. Stop the runs still executing the broken version.
osy cancel-runs --version 1.2.0
# → About to cancel 14 run(s) in flight against version 1.2.0 (app_v3).
#   No compensation runs. Side effects those runs already performed will remain.
#   Continue? [y/n]

# 4. Nothing holds app_v3 now, so the next deploy reclaims it.
osy versions

See also#

Related

Deploying while workflows are running

A workflow run can outlive the deploy that started it. Deploying with --new-version freezes the code and data shape the…

Workflow.Run (start a workflow)

Start the workflow bound to an entity's type, on that entity. Bare — `Workflow.Run(order)` — is fire-and-forget: start…

Workflow.BeginSaga (a compensating saga scope)

Open a saga scope that couples each forward step with its compensation. `await saga.Run("step", step, () => Undo(…))`…