Runlane
Concepts

Rerun And Manual Retry

Linked recovery runs without mutating terminal history.

Rerun and manual retry are operator semantics for creating a new linked run. Neither operation should reactivate the original run.

Rerun

Rerun means "run this task again." Call runlane.runs.rerun(sourceRunId) when an operator intentionally wants another execution of a terminal run's task and payload.

Rerun accepts any terminal source status: succeeded, failed, or cancelled. It rejects active source runs because those runs can still move forward through their own lifecycle.

Use rerun when an operator wants to repeat completed work, replay a task for investigation, or run the same payload again after external conditions changed.

Manual retry

Manual retry means "recover this failed run." Call runlane.runs.retry(sourceRunId) when an operator wants a new recovery attempt for a failed run after automatic retry is exhausted, disabled, or no longer appropriate.

Manual retry is intentionally narrower than rerun. The source run must be failed; retrying a succeeded, cancelled, or still-active source run is rejected. The API name is retry() because it is an operator action, while the persisted child source is RunSourceType.ManualRetry.

Linked child semantics

Both operations create a new child run with its own id, counters, event history, lease lifecycle, outbox rows, and terminal outcome. The child records source.sourceRunId and either source.type = RunSourceType.Rerun or source.type = RunSourceType.ManualRetry, so operator tools can list the recovery history without rewriting the source.

The source run must be readable by the current lane and must belong to the runtime environment. Its task must also be registered in the current runtime. If the task is missing, Runlane cannot safely execute the persisted payload and returns TaskNotFound.

Runlane does not trust the old payload blindly. Before creating the child, core revalidates the source run's persisted payload against the currently registered task schema. If the task schema changed incompatibly, linked creation fails instead of enqueueing a run that workers would reject later.

const replay = await runlane.runs.rerun(sourceRunId)
const recovery = await runlane.runs.retry(failedRunId)

Queue and dispatch

By default, the child uses the source run's queue. You may override it with a registered queue definition:

const recovery = await runlane.runs.retry(failedRunId, {
  queue: recoveryQueue,
})

Queue overrides go through the same queue registry validation as task registration, triggering, and workers. Passing an unknown queue, or a definition that conflicts with the registered policy for that name, is a configuration error.

Linked runs use the runtime trigger dispatch policy. With dispatch.onTrigger: TriggerDispatchMode.Eager, rerun() and retry() persist the child run and immediately flush the matching outbox wakeup. With TriggerDispatchMode.Deferred, they persist the child run and outbox row only; tick() or another maintenance runner publishes it later.

Audit options

The child creation event uses the operator actor by default. Pass actor to record a specific operator identity, meta to attach safe JSON context to the child creation event, and traceCarrier to override trace propagation:

await runlane.runs.rerun(sourceRunId, {
  actor: { type: ActorType.Operator, id: 'ops@example.com' },
  meta: { reason: 'provider_replay' },
  traceCarrier: { traceparent },
})

When traceCarrier is omitted, the child inherits the source run's trace carrier. When actor is omitted, core records ActorType.Operator.

Use runId only when an operator process needs a deterministic child id for external correlation:

await runlane.runs.retry(failedRunId, {
  runId: asId<'run'>('run_retry_2026_05_23_user_123'),
})

If that run id already exists, storage reports the conflict. Linked creation does not use idempotency-key recovery to turn duplicate deterministic ids into a successful replay.

Idempotency, singleton, and concurrency

Linked child runs intentionally do not reuse task idempotency keys. An operator rerun or manual retry means "do new work," not "return the old idempotency owner." The CreateLinkedRunOptions surface therefore has no idempotencyKey option, and task-level idempotency functions are not applied.

Task singleton and concurrency keys still apply when the lane can enforce them. This preserves resource protection for recovery work: linked children can be new work while still avoiding overlap with active work for the same singleton resource or queue concurrency slot.

Why terminal runs stay terminal

Terminal runs are facts. Reactivating them makes history harder to reason about, creates awkward event replay semantics, and can hide the difference between the original failure and the recovery attempt. Linked child runs preserve both stories.

The source run is not mutated. Its status, counters, payload, trace carrier, queue, and event history remain the same before and after linked child creation. Operator UIs should show reruns and manual retries as related children, not as extra attempts on the source run.

On this page