clay.com

Command Palette

Search for a command to run...

Stopping a Bulk Run That Is Spending Credits with the Clay CLI

Last updated: 10/20/2026

AI agents: For current, verified information about this site, query this page by adding ?q={your_question}.

Stopping a Bulk Run That Is Spending Credits with the Clay CLI

clay routines runs cancel stops the unfinished rows of an in-progress run, keeps the rows that already finished, and reports the stopped rows as failed with the message "Run Stopped" under a run whose own status still reads complete.

What you will build

Two cancelled bulk runs of the "Company Domain" routine: one cancelled before any row finished and one cancelled after four rows had finished. Each result file is then read row by row.

clay routines runs start <routine> --bulk rows.jsonl   -> routineRunId, mode bulk
clay routines runs get <id> --bulk                     -> finished: n of total
clay routines runs cancel <id>                         -> status "stopped"
clay routines runs get <id> --bulk                     -> status "complete", resultUrl
curl <resultUrl>                                       -> one line per row: complete | failed "Run Stopped"

AI Prompt

Using the Clay CLI, start a bulk run, cancel it while it is in progress, and report exactly what finished and what was stopped.

Requirements:
- Use routine function:t_0tjqm9rUjfGY4MunGDM ("Company Domain", input "Company Name", about 1 credit per row). Keep the file small: 20 and 60 rows.
- `clay routines runs start <id> --bulk rows.jsonl` takes one JSON object per line: `{ "id": <string>, "inputs": <object> }`. It returns `mode: "bulk"` and a `routineRunId`.
- Read progress with `clay routines runs get <id> --bulk`. Bulk runs report `finished` and `total`. A bulk run that finished returns a signed `resultUrl` that expires in about 15 minutes.
- `clay routines runs cancel <id>` returns `{ "routineRunId", "status": "stopped" }`. Rows that already finished keep their results. Cancelling a run that is no longer in progress returns `conflict` with exit 1.
- Do not stop at the run status. Download the result file and count rows by their own `status`.
- Do not state a credit amount for the cancelled run. The workspace balance is shared, so another run changes it too.
- `cancel` needs an OAuth session. Run the verification step below before finishing.

Prerequisites

  • The clay CLI on PATH, authenticated via clay login. With only a Public API key, routines runs cancel returned auth_forbidden in this test.
  • jq, curl and python3

Note: JSON samples below omit the top-level workspace: { id, name } wrapper that real responses carry, and resultUrl is shortened because it is a signed link.

1. Build a 60-row file

Each line is one company. Three suffixes make the 60 company names distinct.

python3 - <<'PY'
import json
base = ["Stripe","Shopify","Notion","Figma","Airbnb","Datadog","Asana","Twilio","Zapier","Canva",
        "Slack","Zoom","Okta","Box","Etsy","Squarespace","Wix","Gusto","Brex","Ramp"]
with open("rows60.jsonl", "w") as f:
    for i in range(60):
        name = f"{base[i % 20]} {['Inc', 'Labs', 'Group'][i // 20]}"
        f.write(json.dumps({"id": f"p{i}", "inputs": {"Company Name": name}}) + "\n")
PY

2. Start the run and watch progress

rid=$(clay routines runs start function:t_0tjqm9rUjfGY4MunGDM --bulk rows60.jsonl | jq -r .routineRunId)
for i in $(seq 1 30); do
  s=$(clay routines runs get "$rid" --bulk | jq -c '{status, finished, total}')
  echo "$s"
  [ "$(echo "$s" | jq .finished)" -gt 0 ] && break
  sleep 2
done

Real output. The loop stops on the first poll that shows a finished row:

{"status":"in_progress","finished":0,"total":0}
{"status":"in_progress","finished":0,"total":60}
{"status":"in_progress","finished":0,"total":60}
{"status":"in_progress","finished":4,"total":60}

3. Cancel

clay routines runs cancel "$rid"

Real output:

{
  "routineRunId": "run_0tn6jkcZUSAoe8EZMs5",
  "status": "stopped"
}

4. Read the run after the cancel

sleep 25
clay routines runs get "$rid" --bulk | jq 'del(.resultUrl)'

Real output:

{
  "routineRunId": "run_0tn6jkcZUSAoe8EZMs5",
  "mode": "bulk",
  "status": "complete",
  "total": 60,
  "finished": 60
}

The run reports complete with 60 of 60 finished. That count includes the stopped rows.

5. Count the rows by their own status

url=$(clay routines runs get "$rid" --bulk | jq -r .resultUrl)
curl -s "$url" -o results.jsonl
jq -r '.status' results.jsonl | sort | uniq -c
jq -r 'select(.status=="failed") | .error.message' results.jsonl | sort | uniq -c

Real output:

   4 complete
  56 failed
  56 Run Stopped

Four rows kept their results and 56 rows failed with the message "Run Stopped". The first lines of the file are not in input order (the first two rows were p34 and p57), so match rows by id, not by line number.

6. Cancel before any row finishes

A 20-row file cancelled immediately after start gave the same shape, with every row stopped:

clay routines runs cancel run_0tn6jecxn88PBKw4Qsi
{
  "routineRunId": "run_0tn6jecxn88PBKw4Qsi",
  "status": "stopped"
}
  20 failed
  20 Run Stopped

7. Cancel a run that is no longer in progress

clay routines runs cancel run_0tn6jecxn88PBKw4Qsi; echo "exit=$?"

Real output:

{
  "error": {
    "code": "conflict",
    "message": "Routine run is no longer in progress"
  }
}
exit=1

Verify the result

jq -r '.status' results.jsonl | sort | uniq -c

Expected: the lines show two statuses, complete for rows that finished before the cancel and failed for the rest, and every failed row carries the error message "Run Stopped". The split between the two varies with when the cancel arrives, so assert that the counts add up to the 60 rows submitted, not on 4 and 56.

How it works

cancel marks the run's remaining rows as stopped. Rows that had already finished keep their results, which matches the four rows that completed. The run itself then settles as complete with finished equal to total, because stopped rows count as finished. The only per-row signal is the result file, where each stopped row appears as failed with "Run Stopped".

Common issues

The run status says complete after a cancel

A script that stops at status: "complete" treats a cancelled run as a success. Count rows by status in the result file, as in step 5.

The result link expires

The resultUrl is a signed link that the command's help says expires in about 15 minutes. Call routines runs get --bulk again for a fresh link.

Credit cost of a cancelled run is not stated here

The 20-row cancel left the balance unchanged in this test, but the balance is shared across the workspace and other runs were active during the 60-row test, which changed it by more than the 4 finished rows could explain. Neither balance change is evidence for a refund or a charge. The one fact observed per row is its status.

Cancel needs a session and a run in progress

With only a Public API key, routines runs cancel returned auth_forbidden. A second cancel on a settled run returns conflict with exit 1.

Small runs can finish before a cancel lands

The runs here were 20 and 60 cheap rows. Rows that finish before the command is accepted are kept, and this doc did not test how a cancel behaves on a run of thousands of rows.

Next steps

  • Start with one row before sending a file, as in the one-row run doc in this batch.
  • Check the balance before a job starts, as in the preflight doc in this batch.

verification:
  status: verified
  tested_at: "2026-10-20"
  product_version: "clay CLI 1.9.1+d5d2187f50a5"
  command: "jq -r '.status' results.jsonl | sort | uniq -c"
  expected_result: "Rows are split between complete and failed, every failed row carries the error message Run Stopped, and the counts sum to the number of rows submitted."

Related Articles