Runlane
Build with RunlaneControl work

Cancel runs

Request cancellation and help a running task stop safely.

Use cancellation when queued or active work is no longer wanted. Cancellation is cooperative for a running handler; Runlane cannot undo an external side effect that already happened.

Make the handler cancellable

Put cancellation handling inside the task, for example in src/tasks/rebuild-index.ts:

src/tasks/rebuild-index.ts
import { task } from '@runlane/core'
import * as z from 'zod'

const env = z.object({ SEARCH_API_URL: z.url() }).parse(process.env)

export const rebuildIndex = task({
  id: 'search.rebuild-index',
  schema: z.object({ indexId: z.string() }),
  async run({ indexId }, context) {
    if (context.isCancellationRequested()) return

    try {
      const response = await fetch(new URL(`/indexes/${encodeURIComponent(indexId)}/rebuild`, env.SEARCH_API_URL), {
        method: 'POST',
        signal: context.signal,
      })
      if (!response.ok) throw new Error(`Search API returned ${response.status}`)
    } catch (cause) {
      if (context.isCancellationRequested()) return
      throw cause
    }
  },
})

Pass the task signal to I/O APIs that support AbortSignal and check it around long local work.

An aborted I/O call often rejects. Return only when durable state confirms cancellation; rethrow unrelated failures so they remain visible.

Request cancellation

Call runs.cancel() from the application or operator module that authorizes the request:

src/runs/cancel-run.ts
import { type RunId, type RunlaneRuntime } from '@runlane/core'

export async function cancelRun(runlane: RunlaneRuntime, runId: RunId) {
  return runlane.runs.cancel(runId, {
    reason: 'customer_deleted_project',
  })
}

A waiting run can become terminal cancelled immediately. A running run first becomes cancellation_requested; the owning worker observes durable state, aborts context.signal, and records the eventual result.

If the handler cooperates and returns after the request, Runlane records cancelled. If it fails after cancellation was requested, the failure remains a terminal failure rather than being rewritten as a cancellation or retry.

Verify and recover

Read the returned run and then runlane.runs.get(run.id). For active work, expect the intermediate cancellation_requested status before terminal completion. Test that the task observes the signal and stops starting new side effects.

A non-cooperative handler can remain active until it returns or loses its lease. Maintenance can finalize the cancellation after the owner lease expires.

If a run stays cancellation_requested, check worker lease refresh, handler signal use, and maintenance. Repeating the cancel request will not fix those paths.

Cancellation does not compensate completed work. Put refunds, deletes, and other compensation in explicit domain tasks.

Use retry and rerun when the goal is a new run rather than stopping the current one.

On this page