# Step labels (naming a child run so it survives a new version)

> Every child workflow you AWAIT carries a label — a literal string you choose, naming that step. Awaiting parks the run, sometimes for days, which is long enough for a new version of the app to be deployed underneath it; the label is what lets the new version recognise the step the run is sitting at and the work it has already finished. Two steps in one workflow may not share one. A fire-and-forget start never parks, so it needs none.

<!-- id: workflow-park-label · area: workflow · stability: stable · html: https://osysharp.com/reference/workflow/park-label/ -->

## Summary        {#summary}
`await saga.Run("ship", shipment)` and `await Workflow.Run("fulfil", order)` **park** the run: it stops, durably,
until the child finishes. A three-step approval saga can sit parked for a week — and a week is long enough for the app
to be deployed again.

When that happens, the new version has to answer one question about the parked run: **where is it, and what has it
already done?** "Step 2 of 5" is only an answer if both versions agree on what "step 2" *is*. The **label** is that
agreement, and it is why every step carries one.

## Signature      {#signature}
```osy syntax
await Workflow.Run("fulfil", order);                        // label, then the entity
Workflow.Run(order);                                        // fire-and-forget — never parks, so no label
await saga.Run("reserve", hotel);                           // label, then the step
await saga.Run("reserve", hotel, () => Cancel(hotel));      // …and its compensation, as usual
```

The label is a **literal string**. It cannot be a variable or an expression: its whole job is to be the *same* string
in a later version of the body, and a value computed while the run executes could differ between the two readings that
have to match.

## Description    {#description}

### Why it is required rather than inferred   {#required}
The obvious convenience is to derive a label when you leave it out — from the child workflow's name, or from the step
variable. Both are rejected, and the reason is worth stating plainly: **a derived name is not one you chose.** Rename
the child workflow, or rename a local from `hotel` to `outbound`, and the derived label changes — silently, in a way
that has nothing to do with the rename, and that breaks the migration of every run currently parked at that step.

The cost of the alternative is one string literal per step. You are not writing thousands of workflows an hour, and
what the literal buys is that **every step is migratable by construction** — there is no such thing as a run parked
somewhere a new version cannot find.

### Two steps may not share a label   {#unique}
A label is an identity, and two things under one identity is not an identity:

```osy syntax
await saga.Run("leg", outbound);
await saga.Run("leg", inbound);     // compile error
```

> workflow 'BookingSaga': two steps here are both labelled "leg", so a resumed run could not tell which of them it had
> already finished. Give them different labels.

Uniqueness is checked across the **whole workflow** — its start body, every state's `enter` body, and every route —
because a run can be parked at any of them.

### Why that error is at compile time   {#compile-time}
This explains why you are asked now rather than never.

Two indistinguishable steps are only a *problem* when a parked run meets a new version — at **deploy** time, possibly
weeks later. Reporting it then would be useless: the run parked with the duplicate already in place, so relabelling
afterwards cannot help **that** run. It would be a complaint nobody could act on, repeating on every deploy for as long
as the run lived.

Asked at compile time it is the opposite: you have not deployed, no run exists, and typing two names fixes it
permanently.

## Examples       {#examples}

A booking saga with **two legs of the same kind** — an outbound flight and a return. Both run `FlightFlow`, so the
labels are the only thing distinguishing them, and they are what let a run parked on the return leg still be
recognised as "outbound done, return in progress" after a redeploy.

```osy title="the world the saga runs in" test app=wf-park-label
enum BookStatus { Start, Booked, Failed }
enum LegStatus  { Waiting, Done, Bad }

entity Booking {
  [Required, MaxLength(20)] string Ref;
  BookStatus Status = BookStatus.Start;
}

entity FlightLeg {
  [Required] Booking Booking;
  [Required, MaxLength(10)] string Direction;
  LegStatus Status = LegStatus.Waiting;
  bool Cancelled;
}

// The compensation. `Status` belongs to FlightFlow (it is what the workflow `Tracks`), so app code cannot assign it —
// a compensation records its own outcome on a field it owns.
void CancelFlight(FlightLeg leg) { leg.Cancelled = true; }

workflow FlightFlow {
  Tracks = FlightLeg.Status; Autostart = false; Initial = Waiting;
  event Finish();
  state Waiting { subscribe Finish(); on Finish { goto Done; } }
  terminal success Done { }
  terminal error   Bad  { Message = "the leg failed"; }
}
```

```osy title="two legs of one kind, told apart by their labels" test app=wf-park-label
workflow BookingSaga {
  Tracks = Booking.Status; Autostart = false; Initial = Start;
  state Start {
    enter {
      var saga = Workflow.BeginSaga();
      try {
        var outbound = Workflow.Once("make-outbound", () => new FlightLeg { Booking = this.Item, Direction = "out" });
        await saga.Run("outbound-flight", outbound, () => CancelFlight(outbound));

        var inbound = Workflow.Once("make-inbound", () => new FlightLeg { Booking = this.Item, Direction = "back" });
        await saga.Run("return-flight", inbound, () => CancelFlight(inbound));

        saga.Complete();
        goto Booked;
      }
      catch (WorkflowError) { goto Failed; }
      finally { await saga.DisposeUnwind(); }
    }
  }
  terminal success Booked { }
  terminal error   Failed { Message = "the booking failed"; }
}
```

## Notes          {#notes}
- A label names a **step in the body**, not a child run. Two different runs of the same workflow each have their own
  step at that label; the label distinguishes *places in the code*, not instances.
- Labels are compared **exactly**, case included.
- **A fire-and-forget `Workflow.Run(order)` — one written without `await` — takes no label.** Awaiting is what makes
  a start a park point, and only a park point has an identity a later version must match; a start you do not wait on
  has nothing to re-find. `saga.Run` always takes one, because every leg is joined.
- The same reasoning, for a different mechanism, gives [Workflow.Once (run a step at most once)](https://osysharp.com/reference/workflow/once/) its step label. A `Workflow.Once` label keeps
  an *idempotency key* stable across a deploy; a step label keeps a *position* recognisable across one.

## See also       {#see-also}
- [Workflows that outlive the code that started them](https://osysharp.com/reference/workflow/change-over-time/) — why a park point needs a name at all: what a deploy does to a run sitting at one
- [Workflow.BeginSaga (a compensating saga scope)](https://osysharp.com/reference/workflow/beginsaga/) — the saga scope these steps run in
- [Workflow.Run (start a workflow)](https://osysharp.com/reference/workflow/run/) — starting a child workflow, awaited or not
- [Workflow.Once (run a step at most once)](https://osysharp.com/reference/workflow/once/) — the other label, and the other kind of durability
- [Migrating runs that are still in flight](https://osysharp.com/reference/workflow/migration/) — what a deploy does to runs that are already parked
