# Why a workflow run is not moving

> Opens one parked workflow run and says why it is sitting there — which state, which waits are outstanding, who is allowed to act on each of them right now, what would happen next, and when a deadline fires. Then steps it: raise the event it is waiting for and watch the transition.

<!-- id: local-why-a-run-is-not-moving · area: local · stability: stable · html: https://osysharp.com/reference/local/why-a-run-is-not-moving/ -->

## Summary        {#summary}

Answers the question somebody actually asks about a workflow: **this approval has been sitting there for three days —
why?** The run is opened by its own id or by the id of the thing it tracks, and the answer names the waits that are
outstanding and, for each one, the people the workflow's own rules would let act. Then `--raise` moves it.

## Signature      {#signature}

```console
osy workflow-runs                 # the fleet: which workflows, on which version, parked where
osy workflow-runs --runs          # one row per RUN, each with its id
osy workflow-run <id>             # why THAT one is not moving
osy workflow-run <id> --raise <Event> [--slot <Alias>] [--arg N=V] [--as <login>]
```

## Description    {#description}

### Two words that are not the same thing

A **flow** is a durable body suspended mid-execution — an awaited child run, a saga leg, a park point. It has a
**stack**, and a stack is what a debugger steps. That is what [Seeing what happened when your code ran](https://osysharp.com/reference/local/seeing-what-happened/)'s sibling verbs
`osy debug-flows` and `osy debug-attach` address.

A **run** is a workflow state machine. One waiting on a `subscribe` slot or on a clock has **no stack at all** — it is
a row holding a position in a graph, and the only thing that moves it is an **event**. So a debugger cannot attach to
it, and asking for one is asking for frames that do not exist.

The two meet at exactly one point: a run whose *own body* parked mid-execution does own a continuation, and when it
does, this command prints the `osy debug-attach` line for it.

### What the read tells you

Give it a run and it answers in one call:

- **Where it is** — the state, how long it has been there, and the version its definition resolves through. If the
  state is one your current source no longer declares, it says so: that run is executing a definition you can no
  longer read, and `migrate` will not report it because it is already on the current version.
- **What it tracks** — the row, by its readable name, and the value of the property the workflow tracks.
- **Every outstanding wait** — the slot's name, the event it accepts *and that event's parameters*, whether it is
  unassigned or held, and by whom.
- **Who may act** — and this is the field the whole read exists for. See below.
- **What happens next** — one row per move, each naming the wait it fills, with its guard evaluated against this run
  right now. A move whose guard depends on the event's own payload stays offered and is marked as needing input:
  raising it is exactly what supplies the missing value.
- **What is blocking it** — any `Requires` criterion that is not met.
- **Every clock** — the SLAs, the state's `Expire`, the whole-run `Deadline`, and the reminders, each with when it
  fires. A deadline already passed reads as overdue, not as a negative interval.

### "Nobody has picked it up" and "nobody CAN" are different bugs

Every other screen shows an unclaimed wait the same way whoever is looking at it. But an unassigned slot has two
completely different causes:

- a person has not got round to it — someone will;
- the slot's `Candidates` rule admits **nobody** — a lead went on leave, a department was renamed, the only reviewer
  who matched is also the requester. Nobody can ever claim it, and the run will sit until a deadline fires.

So the candidate set is **evaluated now**, through the same rule every claim and deposit is checked against, and an
empty one is stated in words rather than printed as a zero. Where the pool is large the list is a sample and says so.

### Stepping it

`--raise <Event>` raises the event the run is waiting for and prints where it landed.

It runs as a **real principal**: `--as <login>` with that person's own password, exactly like
[Running a function](https://osysharp.com/reference/local/running-a-function/). The workflow's own rules then apply unchanged — an event declaring `[Authorize]` and a
slot declaring `Candidates` refuse a caller who does not satisfy them, and a raise with no principal named is
anonymous, which such a workflow correctly refuses. Holding developer authority over an app admits the *call*; it
never decides what the app's own rules allow.

`--slot <Alias>` names the wait being filled. It is needed whenever two slots subscribe one event — the ordinary
four-eyes shape, where `Approve` sits on both a legal and a finance wait — because the event name alone does not say
which one you are answering. The read prints the exact command to copy, with the slot already in it.

### Getting an id

`osy workflow-runs` groups runs, which is what makes it readable as a fleet view and is also why it carries no ids.
`--runs` lists them individually. You rarely need it: the id of the **tracked row** works too, and that is the one
already in front of you — in a URL, in a page, in the row you were just looking at. Either id may be given by its
first few characters, which is what the listing prints.

## Examples       {#examples}

An approval that has not moved. The run is opened by the purchase order's own id:

```console
$ osy workflow-run a6428528

PoApproval · Review (waiting)   run d77d3242-3b95-4cf4-8b1f-88d1eb42eb24
  PurchaseOrder "Rack of servers" · Status = Review
  in this state 21m · started 22m ago · version 1.0.0

WAITING ON
╭─────────┬─────────┬────────────┬───────────────────────────┬───────────╮
│ slot    │ event   │ status     │ who can act               │ breaches  │
├─────────┼─────────┼────────────┼───────────────────────────┼───────────┤
│ Finance │ Approve │ assigned   │ otto@acme.test (assigned) │ in 3h 38m │
│ Legal   │ Approve │ unassigned │ 3 candidates              │ in 3h 38m │
╰─────────┴─────────┴────────────┴───────────────────────────┴───────────╯
  Legal candidates: liam@acme.test, lena@acme.test, lars@acme.test

WHAT HAPPENS NEXT
  Approve → Rejected (fills Legal)
  Approve → Rejected (fills Finance)
  Cancel → Cancelled

CLOCKS
  reminder on Legal — fires in 1h 38m
  Assigned SLA on Legal — fires in 3h 38m
  run Deadline (whole run) — fires in 29d 23h

Step it:  osy workflow-run d77d3242-… --raise Approve --slot Legal --arg decision=<Decision> --arg reason=<string> --as <login>
```

Nobody is holding the legal wait, and three people could. That is a person, not a rule. Had the pool been empty it
would say so instead, in exactly the place the three names are.

Stepping it as one of them:

```console
$ osy workflow-run d77d3242 --raise Approve --slot Legal \
    --arg decision=Approve --arg reason="within budget" --as lena@acme.test

✓ Raised Approve on PoApproval as lena@acme.test.
  now in Review, waiting on Finance
```

The legal wait is filled and the finance one is not, so the run stays in `Review` — which is the workflow behaving
correctly, and the thing you were checking.

Raising a gated event with nobody named is refused, and the refusal says which rule refused it:

```console
$ osy workflow-run 27b71e0c --raise Cancel

✗ event 'Cancel' requires an authorized principal to raise it.
  This raise was ANONYMOUS. An event with `[Authorize]`, or a slot with `Candidates`, refuses that by design —
  name a principal with `--as <login>`.
```

Finding a run when you have no id at all:

```console
$ osy workflow-runs --runs

╭──────────┬────────────┬────────┬─────────────────┬────────────────┬──────────╮
│ run      │ workflow   │ state  │ item            │ waiting on     │ in state │
├──────────┼────────────┼────────┼─────────────────┼────────────────┼──────────┤
│ 27b71e0c │ PoApproval │ Draft  │ Standing desks  │ Submit         │ 16m      │
│ 33fd08f6 │ PoApproval │ Draft  │ Legal retainer  │ Submit         │ 16m      │
│ d77d3242 │ PoApproval │ Review │ Rack of servers │ Finance, Legal │ 15m      │
╰──────────┴────────────┴────────┴─────────────────┴────────────────┴──────────╯
```

## See also       {#see-also}

- [Candidates (slot)](https://osysharp.com/reference/workflow/candidates/) — the rule that decides who may act on a wait
- [subscribe](https://osysharp.com/reference/workflow/subscribe/) — declaring the wait itself
- [Transitions — where this item may go next](https://osysharp.com/reference/workflow/transitions/) — the same "what happens next" read, from inside the app
- [Seeing what happened when your code ran](https://osysharp.com/reference/local/seeing-what-happened/) — what a run DID, when the question is a fault rather than a wait
- [Running a function](https://osysharp.com/reference/local/running-a-function/) — the other verb that acts as one of the app's own users
