Run recovery
ProA 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.
Preview
Install
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
"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>
| Prop | Type | Default | Description |
|---|---|---|---|
| titlerequired | string | — | What the run was doing. |
| stepsrequired | PlanStep[] | — | Every step of the run, in order. |
| failedStepIdrequired | string | — | The step it stopped at. |
| reasonrequired | string | — | 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. |
| skipNote | string | 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. |
| className | string | 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.
| Key | English |
|---|---|
| label | Stopped run: {title} |
| stoppedLooping | { one: "Stopped after repeating step {step} {count} time without progress.", other: "Stopped after repeating step {step} {count} times without progress.", } |
| stoppedAt | Stopped at step {step} of {total}. |
| kept | { one: "{count} finished step is kept.", other: "{count} finished steps are kept." } |
| confirmLabel | Confirm this change |
| cancel | Cancel |
| confirmRetry | Confirm retry from step {step} |
| confirmSkip | Confirm skipping step {step} |
| skipNote | Skipping goes on to the next step without “{step}”; its work won’t be done. |
| stop | Stop here |
| skip | Skip step {step} |
| retry | Retry from step {step} |