---
question: Does a one-off pipeline run appear on Jobs?
kind: task
via_web: available
via_cli: na
via_python: available
via_api: available
via_config: available
ref_web: System › Jobs
ref_python: JobContext
ref_api: GET /api/admin/system/jobs/runs
ref_config: nats.jobs.retention
prereq: You need a pipeline that a [module job](/user-guide/build/pipelines/schedule-a-pipeline) calls, in a NATS project, and a platform super admin account for the System app and its API.
next:
  - /capabilities/build/pipelines/schedule-a-pipeline
  - /capabilities/build/modules/module-jobs
---

# Monitor runs

A pipeline has no run history of its own. When a module job calls it, each run of that job is recorded: status, attempts, trigger, payload, result, log lines and trace. A one-off `run(...)` from a script, an agent tool or `POST /pipelines/...` is not a job run and is not recorded there.

Run statuses are `QUEUED`, `RUNNING`, `RETRYING`, `SUCCEEDED`, `SKIPPED`, `FAILED`, `TIMED_OUT` and `CANCELLED`.

## Web UI

### Open the Jobs tab

Open the **System** app from the sidebar, then the **Jobs** tab. It lists every job of the engine and of the modules: **Job**, **Schedule**, **Recent runs**, **Last run** and **Now**. A red count on the tab is the number of failed runs; **Show failed runs** filters to them.

![System app on the Jobs tab, filtered](/abi/Screenshot_System_Jobs.png)

*System › Jobs, filtered to one module's jobs: schedule, recent runs, last run and Run.*

### Open the job

Click the job. Its page shows the **Schedule**, **Health**, **Now** and **Settings** (concurrency, attempts, timeout), then its **Runs**. **Run now** starts a run, with an optional JSON payload that reaches the handler as `ctx.payload`.

### Read a run

Click a run. The panel shows the timeline and the **Logs**, **Payload**, **Result** (**Output** when the run did not succeed) and **Trace** tabs. **Re-run** starts it again; **Cancel** stops a running one.

The System app is for platform super admins only.

## CLI

Not applicable. No `abi` command lists jobs or their runs. Use the Web UI or the REST API. `abi dev logs api -f` follows the API process's log, where the engine hosts the jobs: it has what the pipeline writes with `logger`, not the run records.

## Python

### Write to the run's log

The job handler gets a `JobContext`. What it logs and returns is what the Jobs tab shows:

```python title="src/my_module/__init__.py"
@job(triggers=(Every("1h"),))
def sync_items(self, ctx: JobContext) -> dict:
    graph = ItemsPipeline(ItemsPipelineConfiguration()).run(ItemsPipelineParameters())
    if len(graph) == 0:
        ctx.skip("no new items")
        return {}
    ctx.log(f"inserted {len(graph)} triples")
    return {"triples": len(graph)}
```

### Know what each field does

- `ctx.log(message)`: a line in the run's **Logs** tab, up to 200 lines of 2,000 characters.
- `ctx.skip(reason)`: ends the run as `SKIPPED`. Skipped runs are kept for an hour, so polls with nothing to do do not fill the history.
- The return value: the run's **Result**. An exception fails the attempt, and its error shows instead.
- `ctx.cancelled`: an `asyncio.Event`, set on a timeout or a cancel. In a long loop, test `ctx.cancelled.is_set()` (the event object itself is always truthy), or `await ctx.cancelled.wait()` in an async handler.

## REST API

### List the runs

```bash
curl -s "http://localhost:9879/api/admin/system/jobs/runs?module=my_module&job=sync_items&status=FAILED" \
  -H "Authorization: Bearer $NEXUS_ACCESS_TOKEN"
```

Filters are `module`, `job`, `status` (comma-separated), `trigger`, `before`, `limit` (1 to 500, default 50) and `include_skipped`. `module` is the module's name under `modules:`. `GET /api/admin/system/jobs` lists the jobs, and `GET /api/admin/system/jobs/failures?since=...` the failed runs.

### Read one run

```bash
curl -s "http://localhost:9879/api/admin/system/jobs/runs/my_module/$RUN_ID" \
  -H "Authorization: Bearer $NEXUS_ACCESS_TOKEN"
```

The run detail adds `payload`, `result`, `logs` and `trace_url`.

### Trigger or cancel

`POST /api/admin/system/jobs/my_module/sync_items/trigger` with `{"payload": {}}` starts a run and returns `202` with its `run_id`. `POST /api/admin/system/jobs/runs/my_module/$RUN_ID/cancel` cancels one. Every route needs a Nexus login token of a platform super admin; see [Enable in a workspace](/user-guide/build/knowledge-graph/enable-ontology?via=api) for the sign-in call.

## Config

### Keep run records longer

```yaml title="config.yaml"
nats:
  jobs:
    retention:
      enabled: true
      max_age_days: 7
      max_runs_per_job: 1000
      skipped_max_age_minutes: 60
      interval_minutes: 10
```

These are the defaults: finished runs are kept 7 days, at most 1,000 per job, skipped ones 60 minutes, pruned every 10 minutes. `enabled: false` keeps every record. Running and queued runs are never pruned.

### Send traces

Each job run is a span named `job <name>`, and the run record keeps its trace id. With a `telemetry:` block that is enabled (off by default) and sets `ui_url`, the run's **Trace** tab links to the trace viewer. `abi dev up --with-tracing` adds that block. See the [Configuration Reference](/user-guide/get-started/configuration#telemetry).
