Run recovery

Pro

A run that stopped. Shows the steps up to where it broke, what went wrong at that step, and how to carry on: retry from the broken step (earlier work is kept), skip it and go on (when the app allows that), or stop. A loop the agent couldn't leave is named as a loop, with how many times it repeated, rather than reported as a generic failure. A retry or skip that changes something can ask first: confirm says what will happen, and the action runs only once the person confirms.

Markdown

Preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/run-recovery

Brings in: button, locale, step-list, work, utils · npm: lucide-react@^1.67.0

Usage

TSX · The example above
"use client"

import type { PlanStep } from "@/lib/work"
import { RunRecovery } from "@/components/run-recovery"

const steps: PlanStep[] = [
  { id: "s1", title: "Add the region column", status: "succeeded", duration: "14s" },
  { id: "s2", title: "Show it on the customer page", status: "succeeded", duration: "9s" },
  { id: "s3", title: "Backfill from billing country", status: "failed", failure: "Row lock on customers, batch 12" },
  { id: "s4", title: "Test on Staging", status: "idle" },
]

export default function Example() {
  return (
    <RunRecovery
      className="max-w-xl"
      title="Add region to customers"
      steps={steps}
      failedStepId="s3"
      reason="The backfill hit the same row lock on batch 12 four times. Another job may be writing customers."
      loop={{ times: 4 }}
      onRetryFromStep={() => {}}
      onSkip={() => {}}
      onStop={() => {}}
      confirm={{ skip: "Skipping leaves region empty for customers not yet backfilled." }}
    />
  )
}

<RunRecovery>

PropTypeDefaultDescription
titlerequiredstring—What the run was doing.
stepsrequiredPlanStep[]—Every step of the run, in order.
failedStepIdrequiredstring—The step it stopped at.
reasonrequiredstring—What went wrong at that step, in plain words.
loop{ times: number } | undefined—Set when the run was stopped for repeating itself.
onRetryFromStep((id: string) => void) | undefined—Retry from the failed step; earlier finished steps are kept. Without it, there's no retry button.
onSkip((id: string) => void) | undefined—Skip the failed step and go on with the next one. Without it, there's no skip button.
skipNotestring | undefined—What skipping means for this run, shown beside the actions. Defaults to saying the step's work won't be done.
onStop(() => void) | undefined—Stop the run where it is. Without it, there's no stop button.
confirm{ retry?: string | undefined; skip?: string | undefined } | undefined—Ask before a retry or skip that changes something: what it will do, in plain words, shown with a Confirm button. Leave one out to act at once.
classNamestring | undefined—Extra classes for the outer element.

Strings

The fixed words, in English by default. Change any of them under runRecovery in <LocaleProvider strings={{ … }}>; the table is exported as runRecoveryStrings.

KeyEnglish
labelStopped run: {title}
stoppedLooping{ one: "Stopped after repeating step {step} {count} time without progress.", other: "Stopped after repeating step {step} {count} times without progress.", }
stoppedAtStopped at step {step} of {total}.
kept{ one: "{count} finished step is kept.", other: "{count} finished steps are kept." }
confirmLabelConfirm this change
cancelCancel
confirmRetryConfirm retry from step {step}
confirmSkipConfirm skipping step {step}
skipNoteSkipping goes on to the next step without “{step}”; its work won’t be done.
stopStop here
skipSkip step {step}
retryRetry from step {step}