---
title: Job runs and logs
description: Job runs and logs in the dltHub Runtime Python SDK (dlthub_sdk)
keywords: [dlthub, runtime, python sdk, dlthub_sdk, api]
---
# Job runs and logs

<!-- generated from dlthub-client 0.28.8a3, do not edit -->

New to the SDK? [Getting started](getting-started.md) shows how to connect and use it.

## Job runs

### `JobRuns` {#jobruns}

The runs of one workspace, or of one job within it.

#### `JobRuns.get()` {#jobruns-get}

```python
get(*, id: str) -> JobRun
```

Return one run by id.

Job runs are addressed by uuid; to find one by number, filter
`list()` with `number`.

**Arguments**

| Name | Type | Description |
| --- | --- | --- |
| `id` | <code>str</code> | The run's uuid. |

**Returns:** <code><a href="#jobrun">JobRun</a></code> — The run; awaitable in async mode.

**Raises**

| Exception | When |
| --- | --- |
| [`BadRequest`](errors.md#badrequest) | The request was rejected as invalid; `message`, `code` and `fields` say why. |
| [`NotFound`](errors.md#notfound) | No such run in this workspace. |
| [`ScopeMissing`](errors.md#scopemissing) | Reached without a workspace in scope. |

#### `JobRuns.result()` {#jobruns-result}

```python
result(*, id: str) -> JobResult
```

Read one run's declared result, without reading the run itself.

**Arguments**

| Name | Type | Description |
| --- | --- | --- |
| `id` | <code>str</code> | The run's uuid. |

**Returns:** <code><a href="#jobresult">JobResult</a></code> — The result; awaitable in async mode.

**Raises**

| Exception | When |
| --- | --- |
| [`BadRequest`](errors.md#badrequest) | The request was rejected as invalid; `message`, `code` and `fields` say why. |
| [`NotFound`](errors.md#notfound) | No such run, or it declared no result — the platform answers the same either way. |
| [`ScopeMissing`](errors.md#scopemissing) | Reached without a workspace in scope. |

#### `JobRuns.trace()` {#jobruns-trace}

```python
trace(*, id: str) -> JobResultTrace
```

Read one run's whole result, without reading the run itself.

**Arguments**

| Name | Type | Description |
| --- | --- | --- |
| `id` | <code>str</code> | The run's uuid. |

**Returns:** <code><a href="#jobresulttrace">JobResultTrace</a></code> — The whole result; awaitable in async mode.

**Raises**

| Exception | When |
| --- | --- |
| [`ApiError`](errors.md#apierror) | The result is too large to serve (`code` `job_result_trace_too_large`); `result()` still reads the summary fields. |
| [`BadRequest`](errors.md#badrequest) | The request was rejected as invalid; `message`, `code` and `fields` say why. |
| [`NotFound`](errors.md#notfound) | No such run, or it declared no result — the platform answers the same either way. |
| [`ScopeMissing`](errors.md#scopemissing) | Reached without a workspace in scope. |

#### `JobRuns.latest()` {#jobruns-latest}

```python
latest(*, job_id: str | None = None) -> JobRun
```

Return the most recent run.

**Arguments**

| Name | Type | Description |
| --- | --- | --- |
| `job_id` | <code>str &#124; None</code> | Narrow to one job's runs. Omit to use the job in scope, or the whole workspace when there is none, which leaves out system jobs such as the dashboard. |

**Returns:** <code><a href="#jobrun">JobRun</a></code> — The newest run; awaitable in async mode.

**Raises**

| Exception | When |
| --- | --- |
| [`BadRequest`](errors.md#badrequest) | The request was rejected as invalid; `message`, `code` and `fields` say why. |
| [`NotFound`](errors.md#notfound) | Nothing has run yet. |
| [`ScopeMissing`](errors.md#scopemissing) | Reached without a workspace in scope. |

#### `JobRuns.count()` {#jobruns-count}

```python
count(*, job_id: str | None = None) -> int
```

Return how many runs there have been, without fetching them.

**Arguments**

| Name | Type | Description |
| --- | --- | --- |
| `job_id` | <code>str &#124; None</code> | Narrow to one job's runs. Omit to use the job in scope, or the whole workspace when there is none, which leaves out system jobs such as the dashboard. |

**Returns:** <code>int</code> — The count; awaitable in async mode. Saturates at 10,001.

**Raises**

| Exception | When |
| --- | --- |
| [`BadRequest`](errors.md#badrequest) | The request was rejected as invalid; `message`, `code` and `fields` say why. |
| [`ScopeMissing`](errors.md#scopemissing) | Reached without a workspace in scope. |

#### `JobRuns.list()` {#jobruns-list}

```python
list(
    *,
    job_id: str | None = None,
    number: int | None = None,
    limit: int | None = None,
    offset: int = 0,
) -> Iterable[JobRun] | AsyncIterable[JobRun]
```

Yield runs newest first, paging lazily.

Platform errors are raised when iteration begins, not by this call.

**Arguments**

| Name | Type | Description |
| --- | --- | --- |
| `job_id` | <code>str &#124; None</code> | Narrow to one job's runs. Omit to use the job in scope, or the whole workspace when there is none, which leaves out system jobs such as the dashboard. |
| `number` | <code>int &#124; None</code> | Keep only the run with this per-job number. Numbers repeat across jobs, so pair it with a job. |
| `limit` | <code>int &#124; None</code> | Return at most this many. `None` walks to the end, or to row 10,100, past which the platform does not page. |
| `offset` | <code>int</code> | Skip this many, server-side. |

**Returns:** <code>Iterable[<a href="#jobrun">JobRun</a>] &#124; AsyncIterable[<a href="#jobrun">JobRun</a>]</code> — An iterable of runs; async-iterable in async mode.

**Raises**

| Exception | When |
| --- | --- |
| [`BadRequest`](errors.md#badrequest) | The request was rejected as invalid; `message`, `code` and `fields` say why. |
| [`ScopeMissing`](errors.md#scopemissing) | Reached without a workspace in scope. |
| `ValueError` | A negative `limit` or `offset`. |

#### `JobRuns.cancel_all()` {#jobruns-cancel_all}

```python
cancel_all(*, job_refs: Sequence[str], dry_run: bool = False) -> CancelReport
```

Stop whatever is running for the named jobs.

Addressed by job, not by run: cancels every pending, starting or
running run of the named jobs. A ref that names no job, or has nothing
running, is reported in `not_running`.

**Arguments**

| Name | Type | Description |
| --- | --- | --- |
| `job_refs` | <code>Sequence[str]</code> | The jobs whose runs to stop. |
| `dry_run` | <code>bool</code> | Report what would be cancelled without stopping anything. |

**Returns:** <code><a href="#cancelreport">CancelReport</a></code> — What was cancelled and what was not running; awaitable in async mode.

**Raises**

| Exception | When |
| --- | --- |
| `ValueError` | No job refs were given. |
| [`ScopeMissing`](errors.md#scopemissing) | Reached without a workspace in scope. |

### `JobRun` {#jobrun}

One execution of a job.

**Attributes**

| Name | Type | Description |
| --- | --- | --- |
| `number` | <code>int</code> | Number of the run within its job, counting up from 1; numbers repeat across jobs. |
| `id` | <code>str</code> | Unique ID (a UUID). |
| `job_ref` | <code>str</code> | Canonical job reference, unique per workspace. |
| `status` | <code><a href="#jobrunstatus">JobRunStatus</a></code> | The status of the run. |
| `trigger` | <code>str</code> | The trigger the run fired with, e.g. `schedule:0 8 * * *`, `manual:jobs.my_job`, `job.success:jobs.upstream`. |
| `profile` | <code>str</code> | The name of the profile that was used for the run. |
| `started_at` | <code>datetime &#124; None</code> | When the run started; `None` until it has. |
| `finished_at` | <code>datetime &#124; None</code> | When the run finished; `None` until it has. |
| `duration_seconds` | <code>float &#124; None</code> | Duration of the run in seconds, possibly fractional; `None` until it has finished. |
| `max_run_time_seconds` | <code>int &#124; None</code> | Maximum allowed run duration in seconds for this job version. |
| `interval_start` | <code>datetime &#124; None</code> | Start of the interval the run processes; `None` for a job without intervals. |
| `interval_end` | <code>datetime &#124; None</code> | End of the interval the run processes; `None` for a job without intervals. |
| `pipeline_run_summaries` | <code>tuple[<a href="#pipelinerunsummary">PipelineRunSummary</a>, ...]</code> | One entry per dlt pipeline name the run reported; a pipeline that ran more than once shows its latest report. In no particular order, and empty until telemetry has received the reports. |
| `previous_job_run_id` | <code>str &#124; None</code> | The upstream run whose completion started this one, through a `job.success:` / `job.fail:` trigger or a freshness dependency; `None` for a run started manually, by its own schedule, or by a trigger request. |
| `created_at` | <code>datetime</code> | When it was created. |
| `finished` | <code>bool</code> | Whether the status can still change. |
| `failed` | <code>bool</code> | Whether the run finished without succeeding. |

#### `JobRun.cancel()` {#jobrun-cancel}

```python
cancel() -> JobRun
```

Ask the platform to stop this run.

The run moves to `CANCELLING` first, and to `CANCELLED` once it has
stopped.

**Returns:** <code><a href="#jobrun">JobRun</a></code> — A fresh snapshot, usually `CANCELLING` rather than `CANCELLED`; awaitable in async mode.

**Raises**

| Exception | When |
| --- | --- |
| [`BadRequest`](errors.md#badrequest) | The request was rejected as invalid; `message`, `code` and `fields` say why. |
| [`Conflict`](errors.md#conflict) | The current state does not allow the call, e.g. the workspace is archived; `code` and `message` say why. |
| [`NotFound`](errors.md#notfound) | The run no longer exists. |

#### `JobRun.logs()` {#jobrun-logs}

```python
logs() -> Iterable[LogLine] | AsyncIterable[LogLine]
```

Read the run's stored log, oldest line first.

Lines arrive as the body does, so a long log is never held whole. The
platform consolidates a run's log after it ends, so this raises until
that has happened — `stream_logs()` is what follows a live run.
Platform errors are raised when iteration begins, not by this call.

**Returns:** <code>Iterable[<a href="#logline">LogLine</a>] &#124; AsyncIterable[<a href="#logline">LogLine</a>]</code> — An iterable of log lines; async-iterable in async mode.

**Raises**

| Exception | When |
| --- | --- |
| [`NotFound`](errors.md#notfound) | The run has no stored log, either because it does not exist or because it has not been consolidated yet. |
| [`ScopeMissing`](errors.md#scopemissing) | Reached without a workspace in scope. |

#### `JobRun.stream_logs()` {#jobrun-stream_logs}

```python
stream_logs(*, follow: bool = True) -> Iterable[LogLine] | AsyncIterable[LogLine]
```

Follow the run's log live, until the run reaches a terminal status.

Read from the compute backend rather than from storage, so it works
while a run is going and stops on its own when the run ends. The
platform waits up to about a minute for a backend that has not started
reporting yet. Platform errors are raised when iteration begins, not by
this call.

**Arguments**

| Name | Type | Description |
| --- | --- | --- |
| `follow` | <code>bool</code> | Keep reading until the run ends. False replays what the backend has already reported and stops, which the platform marks only by going quiet — so a slow producer can end it early. |

**Returns:** <code>Iterable[<a href="#logline">LogLine</a>] &#124; AsyncIterable[<a href="#logline">LogLine</a>]</code> — An iterable of log lines; async-iterable in async mode.

**Raises**

| Exception | When |
| --- | --- |
| [`ApiError`](errors.md#apierror) | The platform could not follow this run — it is unknown or its backend never reported, the backend does not support live logs, or the stream failed part-way. It reports these on an otherwise successful response, so this can be raised after some lines have already been yielded. |
| [`ScopeMissing`](errors.md#scopemissing) | Reached without a workspace in scope. |

#### `JobRun.wait()` {#jobrun-wait}

```python
wait(*, timeout: float | None = None) -> JobRun
```

Poll until the run stops changing, and return what it ended as.

Settles on any terminal status, so a failed or cancelled run returns
rather than raises — read `status` to find out which. Checks
roughly every second at first and backs off to every ten, so a short run
is noticed almost at once and a long one is not polled thousands of
times.

**Arguments**

| Name | Type | Description |
| --- | --- | --- |
| `timeout` | <code>float &#124; None</code> | Give up after this many seconds. `None`, the default, waits as long as the run takes. |

**Returns:** <code><a href="#jobrun">JobRun</a></code> — A snapshot in a terminal status; awaitable in async mode.

**Raises**

| Exception | When |
| --- | --- |
| [`NotFound`](errors.md#notfound) | The run no longer exists. |
| [`ScopeMissing`](errors.md#scopemissing) | Reached without a workspace in scope. |
| `ValueError` | A negative `timeout`. |
| [`WaitTimeout`](errors.md#waittimeout) | The run was still going when the deadline passed. It is unaffected — nothing cancels it — so this can be retried. |

#### `JobRun.result()` {#jobrun-result}

```python
result() -> JobResult
```

Read the structured result this run declared.

**Returns:** <code><a href="#jobresult">JobResult</a></code> — The result; awaitable in async mode.

**Raises**

| Exception | When |
| --- | --- |
| [`NotFound`](errors.md#notfound) | The run declared none, which is the normal case for a job that reports no result — not a sign the run is missing. |
| [`ScopeMissing`](errors.md#scopemissing) | Reached without a workspace in scope. |

#### `JobRun.trace()` {#jobrun-trace}

```python
trace() -> JobResultTrace
```

Read the whole result this run delivered, trace included.

**Returns:** <code><a href="#jobresulttrace">JobResultTrace</a></code> — The whole result; awaitable in async mode.

**Raises**

| Exception | When |
| --- | --- |
| [`ApiError`](errors.md#apierror) | The result is too large to serve (`code` `job_result_trace_too_large`); `result()` still reads the summary fields. |
| [`NotFound`](errors.md#notfound) | The run declared no result, which is the normal case for a job that reports none — not a sign the run is missing. |
| [`ScopeMissing`](errors.md#scopemissing) | Reached without a workspace in scope. |

### `CancelledRun` {#cancelledrun}

One run a bulk cancel stopped.

**Attributes**

| Name | Type | Description |
| --- | --- | --- |
| `job_ref` | <code>str</code> | Job reference of the cancelled run. |
| `job_run_id` | <code>str</code> | ID of the cancelled run. |
| `job_run_number` | <code>int</code> | Number of the run within its job; numbers repeat across jobs. |
| `previous_status` | <code><a href="#jobrunstatus">JobRunStatus</a></code> | Status of the run when the cancel request arrived. |

### `CancelReport` {#cancelreport}

What a bulk cancel did.

**Attributes**

| Name | Type | Description |
| --- | --- | --- |
| `cancelled` | <code>tuple[<a href="#cancelledrun">CancelledRun</a>, ...]</code> | The runs asked to stop (now cancelling), or that would be in a dry run. |
| `not_running` | <code>tuple[str, ...]</code> | Job refs that had nothing to stop, which is not an error. |

### `JobResult` {#jobresult}

The structured result a run declared.

A run yields at most one, and most do not: a plain pipeline job declares
none, where an agent job reports its own outcome here. The agent fields are
`None` for any other kind of result.

**Attributes**

| Name | Type | Description |
| --- | --- | --- |
| `job_run_id` | <code>str</code> | Job run that declared the result. |
| `job_ref` | <code>str</code> | The job that declared the result. |
| `result_type` | <code>str</code> | `job.{category}.{name}`, for example `job.background_agent.{name}`. |
| `engine_version` | <code>int</code> | Result format version; 1 when not declared. |
| `result` | <code>Any</code> | The job's declared output. Shape is job-defined. |
| `agent_status` | <code>str &#124; None</code> | The agent's self-report, which may legitimately disagree with the run's own status: a failed agent inside a succeeded run is normal. |
| `summary` | <code>str &#124; None</code> | Model-authored markdown, unbounded. Untrusted — render as user content. |
| `model` | <code>str &#124; None</code> | Model the agent ran with. `None` when the result carries no agent trace, as for non-agent jobs. |
| `loop_type` | <code>str &#124; None</code> | Agent loop that ran. `None` when the result carries no agent trace, as for non-agent jobs. |
| `input_tokens` | <code>int &#124; None</code> | Input tokens the agent used. `None` when the result carries no agent trace, as for non-agent jobs. |
| `output_tokens` | <code>int &#124; None</code> | Output tokens the agent used. `None` when the result carries no agent trace, as for non-agent jobs. |
| `total_tokens` | <code>int &#124; None</code> | Total tokens the agent used. `None` when the result carries no agent trace, as for non-agent jobs. |
| `cost_usd` | <code>str &#124; None</code> | Cost in USD the agent reported, as a decimal string. `None` when the result carries no agent trace, as for non-agent jobs. |
| `turn_count` | <code>int &#124; None</code> | Number of turns the agent took. `None` when the result carries no agent trace, as for non-agent jobs. |
| `stop_reason` | <code>str &#124; None</code> | Why the agent stopped. `None` when the result carries no agent trace, as for non-agent jobs. |
| `created_at` | <code>datetime</code> | When the result was first stored, UTC. |
| `updated_at` | <code>datetime</code> | When the result was last stored, UTC. |

### `JobResultTrace` {#jobresulttrace}

The whole job result a run delivered, as the job sent it.

[`JobResult`](#jobresult) carries the summary fields; this carries the whole result
as the job delivered it, which is where an agent's per-turn tool calls live.

**Attributes**

| Name | Type | Description |
| --- | --- | --- |
| `job_run_id` | <code>str</code> | The run that declared it. |
| `result` | <code>Mapping[str, Any]</code> | The result exactly as delivered — the declared output and, for an agent, its trace: turns and the tools each called, token counts, stop reason. Left opaque: the platform stores and returns it verbatim and declares no shape for it. |

### `PipelineRunSummary` {#pipelinerunsummary}

One dlt pipeline a run executed, as telemetry reported it.

A run executes whatever pipelines its job calls, so a job that loads two
sources reports two. A run carries one entry per dlt pipeline name it
reported; a pipeline that ran more than once shows its latest report.
Telemetry fills this in after the pipeline reports, so a run that has just
started carries none, and a run whose job never reached a pipeline carries
none for good.

**Attributes**

| Name | Type | Description |
| --- | --- | --- |
| `pipeline_name` | <code>str</code> | Name of the dlt pipeline. |
| `pipeline_run_id` | <code>str &#124; None</code> | Telemetry ID of the pipeline run. |
| `transaction_id` | <code>str &#124; None</code> | Transaction ID of the pipeline run. |
| `destination_name` | <code>str &#124; None</code> | Name of the destination the pipeline wrote to. |
| `dataset_name` | <code>str &#124; None</code> | Name of the dataset the pipeline wrote to. |
| `status` | <code>str &#124; None</code> | What the pipeline run ended as. Free-form: it is dlt's own wording, not [`JobRunStatus`](#jobrunstatus). |
| `total_rows` | <code>int &#124; None</code> | Total number of rows loaded by the pipeline. |
| `duration_ms` | <code>int &#124; None</code> | Duration of the pipeline run in milliseconds. |
| `started_at` | <code>datetime &#124; None</code> | When the pipeline run started. |
| `finished_at` | <code>datetime &#124; None</code> | When the pipeline run finished. |

### `JobRunStatus` {#jobrunstatus}

Where a run has got to.

**Members**

| Member | Value | Description |
| --- | --- | --- |
| `PENDING` | `'pending'` | Accepted, not yet started. |
| `STARTING` | `'starting'` | Being placed on a compute backend. |
| `RUNNING` | `'running'` | Executing. |
| `CANCELLING` | `'cancelling'` | Cancellation asked for, not yet effective. |
| `COMPLETED` | `'completed'` | Finished successfully. |
| `FAILED` | `'failed'` | Finished unsuccessfully. |
| `CANCELLED` | `'cancelled'` | Stopped on request, or because it exceeded its maximum run time. |
| `SKIPPED` | `'skipped'` | Recorded without running. A start the platform declines is normally reported through [`TriggerStatus`](jobs.md#triggerstatus) and creates no run. |
| `UNKNOWN` | `'unknown'` | A status this SDK version does not know. |

## Logs

### `LogLine` {#logline}

One line a run emitted.

**Attributes**

| Name | Type | Description |
| --- | --- | --- |
| `content` | <code>str</code> | Contents of the log message. |
| `line_num` | <code>int</code> | Position of the line in the run's log. |
| `source` | <code><a href="#logsource">LogSource</a></code> | Who emitted the line. |
| `stage` | <code><a href="#logstage">LogStage</a></code> | Stage of the run the line belongs to: `build` (installing dependencies before the job starts) or `run` (the job's execution). |
| `io_stream` | <code><a href="#logiostream">LogIoStream</a></code> | Stream the line was written to: `stdout`, `stderr`, or `other` (a provider's info channel, or a line stored before streams were kept). |
| `reported_at` | <code>datetime</code> | Timestamp when the log was reported. |

### `LogIoStream` {#logiostream}

The stream a log line was written to.

**Members**

| Member | Value | Description |
| --- | --- | --- |
| `STDOUT` | `'stdout'` | Standard output. |
| `STDERR` | `'stderr'` | Standard error. |
| `OTHER` | `'other'` | Neither: an info channel of the compute backend, or a line stored before the stream was recorded. |
| `UNKNOWN` | `'unknown'` | A stream this SDK version does not know. |

### `LogSource` {#logsource}

Who emitted a log line.

**Members**

| Member | Value | Description |
| --- | --- | --- |
| `USER` | `'user'` | The user's own code. |
| `SETUP` | `'setup'` | The install and build commands that prepare the run. |
| `RUNNER` | `'runner'` | The platform's bootstrap around the user's code. |
| `UNKNOWN` | `'unknown'` | A source this SDK version does not know. |

### `LogStage` {#logstage}

Which stage of the run a log line belongs to.

**Members**

| Member | Value | Description |
| --- | --- | --- |
| `BUILD` | `'build'` | Preparing the environment the code runs in. |
| `RUN` | `'run'` | Executing the code. |
| `UNKNOWN` | `'unknown'` | A stage this SDK version does not know. |
