# Cancel a run

> Stop a run that is running or waiting, along with the agent turns inside it.

This guide stops a run that's running or waiting, along with the agent turns inside it.

## Before you start

- **A run that's running or waiting,** and its id. `kindgi runs list` lists runs, as [List runs](https://docs.kindgi.com/v0.1.9/guides/runs/list-runs.md) shows.
- **`kindgi dev` running** in your pack's directory, with Kindgi 0.1's CLI.
- **For the app's half:** Node 22.12 or later, Python 3.11 or later, or Java 17 or later, with Kindgi 0.1's client, and `KINDGI_API_URL` and `KINDGI_API_TOKEN` set.

## Cancel it

A cancel stops the run wherever it is, and ends it as `cancelled`.

- **TypeScript**

  ```sh
  pnpm exec kindgi runs cancel <run-id>
  ```

- **Python**

  ```sh
  uv run kindgi runs cancel <run-id>
  ```

- **Java**

  ```sh
  ./kindgiw runs cancel <run-id>
  ```

- **Scala**

  ```sh
  ./kindgiw runs cancel <run-id>
  ```

`<run-id>` is the run's id. It prints:

```json
{
  "ok": true,
  "runId": "265627c7-1ac7-4d18-a926-6da9fcc18577"
}
```

The run's status is now `cancelled`, and its journal ends with `run.cancelled`:

```json excerpt
{"sequence": 6, "kind": "wait.suspended", "nodeId": "hold", "payload": {"nodeId": "hold", "tokenId": "child:d8c2c7dc-7ceb-4e4b-8986-ee75e7c9bcc2:25"}, …}
{"sequence": 7, "kind": "run.cancelled", "payload": {}, …}
```

That run was waiting for an approval. A run can be cancelled while it runs too, and cancelling a flow's run cancels the agent turns inside it. [What a cancel stops](https://docs.kindgi.com/v0.1.9/guides/runs/cancel-a-run.md#what-a-cancel-stops) has the details.

A run that already ended can't be cancelled:

```text
Error [conflict]: Cannot cancel run: already cancelled
```

## What a cancel stops

A cancel takes effect at once, wherever the run is:

- **No step starts after it.** A flow between two steps, or in the middle of a loop, starts nothing more, and its journal records no start after `run.cancelled`.
- **A step running when it lands stops.** An agent turn stops waiting for its model and ends `cancelled`. A start that was waiting for the turn (`wait: true`) answers within seconds with `422 agent-turn-aborted`, "Agent turn cancelled".
- **A step that ends as it lands keeps its end.** Its `step.completed` comes after `run.cancelled` in the journal: it did finish.

Here a flow running a loop over 2,000 orders, each answered by an agent, was cancelled twice. The first time, the turn for order A-1012 was running: the cancel stopped it, and the journal ends at the cancel:

```json excerpt
{"sequence": 77, "kind": "step.started", "nodeId": "answer", "payload": {"input": "A-1012", …}, …}
{"sequence": 78, "kind": "run.cancelled", "payload": {}, …}
```

The second time, the turn for A-1030 finished as the cancel landed. Its end follows `run.cancelled`, and nothing starts after it:

```json excerpt
{"sequence": 185, "kind": "step.started", "nodeId": "answer", "payload": {"input": "A-1030", …}, …}
{"sequence": 186, "kind": "run.cancelled", "payload": {}, …}
{"sequence": 187, "kind": "step.completed", "nodeId": "answer", "payload": {"output": {"text": "You said: \"A-1030\"", …}, …}, …}
{"sequence": 188, "kind": "edge.evaluated", "payload": {"to": "$end", "from": "answer", "edgeId": "b1", "decision": true, …}, …}
{"sequence": 189, "kind": "iteration.completed", "nodeId": "each", "payload": {…, "iteration": 30, …}, …}
```

A step that was running when the run was cancelled, such as the loop itself or A-1012's step, has no end of its own: `run.cancelled` ends it, as it ends a run waiting for an approval.

An agent turn started with `wait: true`, and cancelled while its model call ran, answers the waiting start with:

```json
{
  "error": {
    "code": "agent-turn-aborted",
    "message": "Agent turn cancelled",
    "details": {
      "reason": "external",
      "runId": "8619fb26-6370-48df-b9bc-01bcd91602b6"
    },
    "requestId": "req-fb51fd31-fe44-4a44-ad97-88baa95a23e4"
  }
}
```

## From your app

Your app cancels a run with `runs.cancel`. Ask your coding agent: "I want my app to cancel a run it started." For reference, this code starts a run in the background, then cancels it:

- **TypeScript**

  `app/cancel-reservation.ts`

  ```ts
  import { createClient } from '@kindgi/sdk/client';

  const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN

  const run = await kindgi.runs.start({
    flow: 'acme.reserve-order',
    input: { orderId: 'A-500' },
    options: { wait: false },
  });
  const cancelled = await kindgi.runs.cancel(run.id);
  console.log(cancelled.status);
  ```

  ```text
  cancelled
  ```

- **Python**

  `app/cancel_reservation.py`

  ```python
  from kindgi.client import Kindgi

  kindgi = Kindgi()  # KINDGI_API_URL, KINDGI_API_TOKEN

  run = kindgi.runs.start(
      flow="acme.reserve-order", input={"orderId": "A-500"}, options={"wait": False}
  )
  cancelled = kindgi.runs.cancel(run.id)
  print(cancelled.status)
  ```

  ```text
  cancelled
  ```

- **Java**

  `src/main/java/com/acme/app/CancelRun.java`

  ```java
  package com.acme.app;

  import com.kindgi.client.Kindgi;
  import com.kindgi.client.models.Run;
  import com.kindgi.client.models.StartRunBody;
  import java.util.Map;

  public final class CancelRun {
    public static void main(String[] args) {
      Kindgi kindgi = Kindgi.create(); // KINDGI_API_URL, KINDGI_API_TOKEN

      Run run = kindgi.runs().start(StartRunBody.WithFlow.builder()
          .flow("acme.reserve-order")
          .input(Map.of("orderId", "A-500"))
          .options(StartRunBody.WithFlow.Options.builder().wait_(false).build())
          .build());
      Run cancelled = kindgi.runs().cancel(run.id());
      System.out.println(cancelled.status());
    }
  }
  ```

  ```text
  cancelled
  ```

- **Scala**

  `src/main/scala/acme/app/CancelRun.scala`

  ```scala
  package acme.app

  import com.kindgi.client.Kindgi
  import com.kindgi.client.models.StartRunBody
  import scala.jdk.CollectionConverters._

  object CancelRun {
    def main(args: Array[String]): Unit = {
      val kindgi = Kindgi.create() // KINDGI_API_URL, KINDGI_API_TOKEN

      val run = kindgi.runs().start(StartRunBody.WithFlow.builder()
        .flow("acme.reserve-order")
        .input(Map("orderId" -> "A-500").asJava)
        .options(StartRunBody.WithFlow.Options.builder().wait_(false).build())
        .build())
      val cancelled = kindgi.runs().cancel(run.id)
      println(cancelled.status)
    }
  }
  ```

  ```text
  cancelled
  ```

`cancel` returns the run, now `cancelled`. It answers with a conflict error when the run had already ended.

## Approvals of a cancelled run

Cancelling a run withdraws the approvals it was waiting for, in the same step. So does it for the approvals of the runs inside it, such as a flow's agent steps. They leave the pending list as `withdrawn`, with `withdrawnBecause: "run-cancelled"`. A reviewer who opens one can no longer decide it: it answers `409 approval-terminal`.

An approval the run asked for without waiting on it stays pending, and can still be decided. A project-wide memory fact's review is one, and a memory erasure's is another.

To see them before you cancel, list the run's approvals with the runs inside it: `approvals.list({ runId, includeDescendants: true })` in TypeScript, `approvals.list(run_id=…, include_descendants=True)` in Python.

[Decide an approval](https://docs.kindgi.com/v0.1.9/guides/approvals/decide-an-approval.md) shows how reviewers decide them.

## In the console

A run's page has **Cancel run** while it's going, for project editors and up. Its dialog says what a cancel does before anything happens:

```text
Cancel this run?
This stops the run and every step still going inside it. It ends as cancelled, and can't be continued.
```

- **A run waiting for an approval** adds: "If it's waiting for an approval, that approval is withdrawn: it leaves the reviewers' list." See [Approvals of a cancelled run](https://docs.kindgi.com/v0.1.9/guides/runs/cancel-a-run.md#approvals-of-a-cancelled-run).
- **Keep it running** closes the dialog. **Cancel the run** cancels it, and the dialog says "Cancelled.".
- **A run that finished meanwhile:** "It had already finished: there was nothing to stop."

![The Cancel this run? dialog over a running flow run's page: "This stops the run and every step still going inside it. It ends as cancelled, and can't be continued.", with Keep it running and Cancel the run.](https://docs.kindgi.com/v0.1.9/_astro/run-cancel-dialog.vz9zHuv6_roJYB.webp)

### After a cancel

The run's page says **Cancelled**, and so does each step that was still going when the cancel landed: a loop's node, and the round that was running. Those steps have no end of their own in the journal, as [What a cancel stops](https://docs.kindgi.com/v0.1.9/guides/runs/cancel-a-run.md#what-a-cancel-stops) explains. So the Overview shows each with a plain circle and "Cancelled", not as still running. A step that finished as the cancel landed keeps its end.

Here a loop over six orders was cancelled while the turn for A-1002 ran:

![A cancelled flow run's Overview: acme.each-order, Cancelled, its input of six orders, then under Steps "each Cancelled" and "answer Cancelled", each with a plain circle.](https://docs.kindgi.com/v0.1.9/_astro/run-cancelled-steps.Bjs1f7Oh_Z210rw7.webp)

The **Journal** tab says so beside each of those starts. Its timeline for that run, without the times:

```text
The run started
Started: step "each"    Cancelled with the run: no end of its own
Done: step "answer"     1.7 s
Done: step "answer"     1.6 s
Started: step "answer"  Cancelled with the run: no end of its own
The run was cancelled
```

A failed run shows its unfinished steps the same way: "Stopped: the run failed" on the Overview, and "Stopped when the run failed: no end of its own" in the Journal.

## Next

- [Retry a start safely](https://docs.kindgi.com/v0.1.9/guides/runs/retry-a-start-safely.md)
- [Read a run's journal](https://docs.kindgi.com/v0.1.9/guides/runs/read-the-journal.md)
- [See why runs failed](https://docs.kindgi.com/v0.1.9/guides/runs/see-why-runs-failed.md)
