> ## Documentation Index
> Fetch the complete documentation index at: https://boxrouter.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Find a Run

> Find a GitHub Actions run in BoxRouter, follow its sandbox and cost, and cancel or re-run it.

**GitHub Actions** in the dashboard lists every workflow run that used a BoxRouter runner. Open one to see where each job ran, its timeline, and what it cost. Jobs in the same workflow that use other runners are not listed.

**Before You Begin:** A repository turned on for Actions and at least one job that has run on a BoxRouter label.

## Find the Run

Switch between **Active** and **All**, search by workflow or repository, or filter by repository, state, branch, or trigger. Select a run to open its panel beside the list. A workflow with several jobs shows one block per job, with its provider, size, region, and sandbox.

## Read the Panel

* The timeline lists the steps below in the order they happen. The dashboard shows the label, and `boxrouter actions jobs list-events` and the API return the step name, with an `outcome` (`ok`, `refused`, `ignored`, `deferred`, `unknown` or `error`) and a `reason_code`. A job that stops early may not reach every step; a refusal shows the reason.

  | Step (API) | Dashboard label |
  | - | - |
  | `webhook_received` | Job requested |
  | `admission` | Accepted |
  | `registration` | Runner registered |
  | `image_verification` | Image verified |
  | `controller_pass` | Scheduled |
  | `sandbox_create` | Sandbox created |
  | `launch` | Runner started |
  | `assignment` | Job assigned |
  | `result` | Result reported |
  | `cleanup` | Sandbox deleted |

* A job's cost and charge read `unknown` after the job ends, and its full reservation stays held, until the provider reports its usage. Some providers take about 30 minutes. The charge is then set from the provider's records and the unused reservation returns; see [Read a Sandbox's Cost](https://boxrouter.ai/docs/cost.md#read-a-sandboxs-cost).

* The job's sandbox page says **Created by GitHub Actions** and links back to the run. It keeps its record after the sandbox is deleted.

* Opening the panel shows the first part of the job log, fetched from GitHub at that moment. BoxRouter doesn't store it. **View job log** opens the full log on GitHub, which owns step output.

## From Code or the Terminal

[Install a client](https://boxrouter.ai/docs/quickstart.md) and set `BOXROUTER_API_KEY` as in [Set Up Your Account](https://boxrouter.ai/docs/setup.md). The CLI
prints JSON, and these examples print the same JSON from the SDKs.

First, list the recent failed runs, read the first one, and list its jobs:

```python Python theme={"theme":"css-variables"}
import json
from boxrouter import BoxRouter

with BoxRouter() as client:
    actions = client.api.actions
    runs = actions.runs.list(state=["failed"], limit=5)  # recent failed runs
    run_id = runs.data[0].id
    run = actions.runs.get(run_id)  # result, failure and billing
    print(json.dumps(run.to_dict()))
    jobs = actions.runs.list_jobs(run_id)  # each job: provider, size, cost, failure
    print(json.dumps(jobs.to_dict()))
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  const actions = client.api.actions;
  const runs = await actions.runs.list({ query: { state: ['failed'], limit: 5 } }); // recent failed runs
  const runId = runs.data[0].id;
  const run = await actions.runs.get({ path: { run_id: runId } }); // result, failure and billing
  console.log(JSON.stringify(run));
  const jobs = await actions.runs.listJobs({ path: { run_id: runId } }); // each job: provider, size, cost, failure
  console.log(JSON.stringify(jobs));
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
RUN_ID=$(boxrouter actions runs list --state failed --limit 5 | jq -r '.data[0].id')  # recent failed runs
boxrouter actions runs get "$RUN_ID"                       # result, failure and billing
boxrouter actions runs list-jobs "$RUN_ID"                 # each job: provider, size, cost, failure
```

Then read one job. Replace `JOB_ID` with the `id` of a job from the list:

```python Python theme={"theme":"css-variables"}
import json
from boxrouter import BoxRouter

with BoxRouter() as client:
    actions = client.api.actions
    steps = actions.jobs.get_steps("JOB_ID")  # step results and durations
    print(json.dumps(steps.to_dict()))
    events = actions.jobs.list_events("JOB_ID")  # the timeline above
    print(json.dumps(events.to_dict()))
    logs = actions.jobs.get_logs("JOB_ID")  # the log prefix
    print(json.dumps(logs.to_dict()))
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  const actions = client.api.actions;
  const steps = await actions.jobs.getSteps({ path: { job_id: 'JOB_ID' } }); // step results and durations
  console.log(JSON.stringify(steps));
  const events = await actions.jobs.listEvents({ path: { job_id: 'JOB_ID' } }); // the timeline above
  console.log(JSON.stringify(events));
  const logs = await actions.jobs.getLogs({ path: { job_id: 'JOB_ID' } }); // the log prefix
  console.log(JSON.stringify(logs));
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter actions jobs get-steps JOB_ID      # step results and durations
boxrouter actions jobs list-events JOB_ID    # the timeline above
boxrouter actions jobs get-logs JOB_ID       # the log prefix
```

The first example takes the first run's `id` and prints that run's jobs; each job's `id` is the value for `JOB_ID`. The separate `github_run_id` and `github_job_id` fields do not work in these commands. An empty `data` array means there are no matching runs or jobs, and the first example stops.

A job's `lifetime_seconds` is its [time limit](https://boxrouter.ai/docs/actions/runners.md#time-limits) plus the 5-minute
pickup window, in seconds: 21,900 for a job with the 6-hour limit. Use `get-steps` for
individual step durations; the run panel shows the overall run duration. [Actions in the API
reference](https://boxrouter.ai/docs/reference/list-actions-runs.md) lists every field.

## When a Job Did Not Succeed

A failed job shows its reason code, which you can copy into a support request. Amber marks a BoxRouter or provider problem; red marks a failed workflow step. **Why it failed** uses the first matching category in this order:

| Category | Recorded As | Next Step |
| - | - | - |
| Provider Refusal | An event with `outcome: refused`, or `failure.reason_code: no_eligible_provider` | Check the provider key, credits and size, then re-run. See [Troubleshoot Actions](https://boxrouter.ai/docs/actions/troubleshooting.md#refusals). |
| Cancelled | The job result `cancelled` | Nothing to fix. |
| Timeout | The job result `timed_out` | Shorten or split the job. See [time limits](https://boxrouter.ai/docs/actions/runners.md#time-limits). |
| BoxRouter Failure | `failure.kind: boxrouter`, or the result `infrastructure_failure` or `attention_required` | Re-run. If it fails again, send the run link and reason code. |
| Workflow Failure | `failure.kind: workflow`, or the result `failure` or `workload_failure` | Open the job log on GitHub. |

If no event, recorded failure or job result matches a row above, the panel shows no failure category.

## Cost

The panel shows what a job's sandbox cost, and whether your provider key or Credits paid for it.

* **Charge** appears once a run paid with Credits ends. The panel and the runs table both show it. It is the provider's cost with no markup. A run still going, or one whose charge isn't settled, shows a dash.
* The sandbox's page shows how the charge was worked out. Runner setup, pickup wait, and cleanup count as sandbox usage.
* **Provider Cost** is the sandbox's cost, from its creation until BoxRouter confirmed its destruction. A workflow with several jobs shows **Job Provider Cost** for each.
* **Reported By Provider** means the provider reported the amount. Otherwise, the label says the amount was calculated from a list price.
* A run still in progress shows no amount until its sandboxes are deleted.
* A job refused for funding starts nothing and is charged nothing. After you [add credits](https://boxrouter.ai/docs/credits.md), re-run it; it does not start by itself.
* A missing amount means BoxRouter has neither a provider-reported cost nor a calculated cost to show. Check your provider's bill for the amount charged.

With a provider key, BoxRouter charges nothing for the job and the provider bills you directly. GitHub's own duration and any GitHub-hosted runner charges are separate. See [Cost](https://boxrouter.ai/docs/cost.md) for labels and sources.

## Cancel and Re-Run

**Cancel Run**, **Re-run** and **Re-run Failed Jobs** appear in the panel and the row menu when you have permission. Each sends the request to GitHub. When you select **Cancel Run**, BoxRouter asks GitHub to stop the whole workflow run, including its other jobs. BoxRouter requests cleanup of its sandboxes; check the run's cleanup status for confirmation.

A re-run starts a new attempt in a new sandbox. BoxRouter checks the repository, trigger, label and funding again, then picks a provider and account again. A re-run can therefore run elsewhere than the first attempt, or be refused.

After you cancel or re-run, the panel shows *requested* until GitHub reports the new state. Check the run on GitHub before you click again, because each click sends another request.

Cancel a run by its `id` from `runs list`:

```python Python theme={"theme":"css-variables"}
from boxrouter import BoxRouter

with BoxRouter() as client:
    client.api.actions.runs.cancel("RUN_ID")
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  await client.api.actions.runs.cancel({ path: { run_id: 'RUN_ID' } });
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter actions runs cancel RUN_ID
```

Once GitHub reports the run as finished, re-run only its failed jobs:

```python Python theme={"theme":"css-variables"}
from boxrouter import BoxRouter

with BoxRouter() as client:
    client.api.actions.runs.rerun_failed_jobs("RUN_ID")
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  await client.api.actions.runs.rerunFailedJobs({ path: { run_id: 'RUN_ID' } });
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter actions runs rerun-failed-jobs RUN_ID
```

## Next Steps

> [card] [Troubleshoot Actions](https://boxrouter.ai/docs/actions/troubleshooting.md)
>
> Fix a refused or failed job.

> [card] [How Sandbox Cost Works](https://boxrouter.ai/docs/cost.md)
>
> Cost labels and sources.

> [card] [Choose a Runner](https://boxrouter.ai/docs/actions/runners.md)
>
> Labels, sizes, and time limits.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.