# useFlightRun hook
> React hook for following a Flight run's status from a MotherDuck Dive until it finishes.
The `useFlightRun` hook follows a [Flight](/key-tasks/flights/) run from a [Dive](/key-tasks/dives/) until it finishes, for example the run [`useRunFlight`](../use-run-flight) just started. Use it to show progress and to refetch the Dive's data when the run succeeds, instead of polling `MD_GET_FLIGHT_RUN` yourself.

## Import

```jsx
import { useFlightRun } from "@motherduck/react-sql-query";
```

## Syntax

```jsx
const { status, isFinished, exitCode, isChecking, error, check } = useFlightRun(flightId, runNumber, options);
```

## Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `flightId` | `string` | Yes | The Flight's ID, a UUID. |
| `runNumber` | `number \| undefined` | Yes | The run to follow, usually `useRunFlight`'s `data?.runNumber`. While it's `undefined`, nothing is checked. |
| `options.onFinished` | `(run) => void` | No | Called once per run when the hook sees it finished, with `{ status, exitCode, startedAt, endedAt }`. It also fires for a run that had already finished when the hook started following it. |

## Return value

| Property | Type | Description |
|----------|------|-------------|
| `status` | `'PENDING' \| 'RUNNING' \| 'SUCCEEDED' \| 'FAILED' \| 'CANCELLED' \| undefined` | The run's status. `undefined` until the first check returns. |
| `isFinished` | `boolean` | `true` once the status is `SUCCEEDED`, `FAILED`, or `CANCELLED`. |
| `exitCode` | `number \| null` | The process exit code, once known. `0` means success. |
| `startedAt`, `endedAt` | `Date \| null` | When the run started and ended, once known. A cancelled run's `endedAt` is when it was cancelled. |
| `isChecking` | `boolean` | `true` from the first render with a run number until the hook stops following the run. Use it with `useRunFlight`'s `isPending` to disable a button. |
| `error` | `Error \| null` | Why the hook stopped following the run, if it stopped early: no access, the run wasn't found, repeated failed checks, or the Dive couldn't connect. |
| `check` | `() => void` | Checks the run again after the hook has stopped. |

## Behavior

- **Checking schedule.** The hook checks every 2 seconds for the first 30 seconds, then every 5 seconds up to 5 minutes, then every 30 seconds. Each check waits for the previous one.
- **When it stops.** It stops when the run finishes, after 3 failed checks in a row, and after an hour. It stops at once, without retrying, when MotherDuck reports that the run doesn't exist or the viewer may not read it, and when the Dive's connection fails. A check that returns no row (`Run <n> of Flight <id> wasn't found.`) counts as a failed check, so it's retried. Call `check()` to check again.
- **Who can follow a run.** The Flight's owner and organization Admins can follow any of its runs. Someone with a [`run` grant](/sql-reference/motherduck-sql-reference/flights/grant-run-on-flight) can follow only the runs they started, which covers the runs they start from the Dive.
- **No config or logs.** The hook reads only a run's status, exit code, and times, never its config values or logs.
- **Errors stay in the Dive.** Failed checks don't open the query error overlay, so show `error` yourself.
- **Input checks.** A Flight ID that isn't a UUID fails with `useFlightRun: the Flight id must be a UUID, got <value>.`, and a run number that isn't a whole number fails with `useFlightRun: the run number must be a whole number, got <value>.`

## Example

```jsx
import { useSQLQuery, useRunFlight, useFlightRun } from "@motherduck/react-sql-query";

const REFRESH_CRM_FLIGHT = "7b1e2c3d-0000-4000-8000-000000000001";

export default function Dive() {
  const accounts = useSQLQuery(`SELECT * FROM "crm"."main"."accounts" LIMIT 50`);
  const refresh = useRunFlight(REFRESH_CRM_FLIGHT);
  const latest = useFlightRun(REFRESH_CRM_FLIGHT, refresh.data?.runNumber, {
    onFinished: (run) => {
      if (run.status === "SUCCEEDED") accounts.refetch();
    },
  });
  const busy = refresh.isPending || latest.isChecking;

  return (
    <div className="p-6">
      <button className="rounded border px-3 py-1" disabled={busy} onClick={() => refresh.run()}>
        {busy ? "Refreshing…" : "Refresh data"}
      </button>
      {latest.isFinished && latest.status !== "SUCCEEDED" && <p>The refresh didn't finish.</p>}
      {latest.error && <p>Couldn't check the refresh: {latest.error.message}</p>}
    </div>
  );
}
```

## Related

- [`useRunFlight`](../use-run-flight) — start a run.
- [Add write-back to a Dive](/key-tasks/dives/run-flights-from-dives)
- [`MD_GET_FLIGHT_RUN`](/sql-reference/motherduck-sql-reference/flights/md-get-flight-run) — read a run from SQL.


---

## Docs feedback

MotherDuck accepts optional user-submitted feedback about this page at `GET https://motherduck.com/docs/api/feedback/agent`.
For agents and automated tools, feedback submission should be user-confirmed before sending.

URL-encode query parameter values and send a GET request:

```text
GET https://motherduck.com/docs/api/feedback/agent?page_path=%2Fsql-reference%2Fmotherduck-sql-reference%2Fdives%2Fuse-flight-run%2F&page_title=useFlightRun%20hook&text=<url-encoded user feedback, max 2000 characters>
```

Optionally append `&source=<url-encoded interface identifier>` such as `claude.ai` or `chatgpt`.

`page_path` and `text` are required; `page_title` and `source` are optional. Responses: `200 {"feedback_id": "<uuid>"}`, `400` for malformed query parameters, and `429` when rate-limited.
