# Analyzing CI (/sling-cli/commands/analyze)



Five commands that answer analytical questions rather than listing records. Each is a first-class product surface, not a dashboard export.

## `sling usage`

```text
sling usage [--org <slug> | --repo <owner/name>] [--group-by <axis>] [--order-by <col>]
            [--asc | --desc] [window] [--json | --agent]
```

Attributes StarSling runner minutes and dollar cost over a time window.

| Flag                | Meaning                                                                                           |
| ------------------- | ------------------------------------------------------------------------------------------------- |
| `--org`, `--repo`   | Scope. `--repo owner/name` narrows to one repo; mutually exclusive. Defaults to your default org. |
| `--group-by`        | `label` (default) · `workflow` · `job` · `repo` · `day`                                           |
| `--order-by`        | `cost` (default) · `minutes` · `jobs` · `key`                                                     |
| `--asc` / `--desc`  | Sort direction, default `--desc`. Naming both is a usage error. Not valid with `--group-by day`.  |
| `--window <n>d`     | Trailing window, e.g. `30d`. Defaults to 30 days.                                                 |
| `--month <YYYY-MM>` | A calendar month.                                                                                 |
| `--from` / `--to`   | An explicit half-open range `[from, to)`. Both required together.                                 |
| `--json`, `--agent` | Machine mode.                                                                                     |

```console
$ sling usage --org mastra-ai --window 7d --group-by repo
StarSling usage — by repo
2026-07-07 → 2026-07-14

KEY                     RUNNER MIN    JOBS     COST $  % TOTAL
mastra-ai/mastra           25459.0   10784    $202.85    89.3%
mastra-ai/docs              3040.2    1120     $24.22    10.7%
```

The window flags are **mutually exclusive** — passing two is a usage error, not a silent precedence rule. A single-row result omits `% TOTAL`, since a lone row's share is always 100%. `--group-by day` renders a burn-down chart instead of a table.

<Callout type="info">
  Cost is `minutes × vCPU × $0.002`, where minutes are **billed** minutes — `max(1, ceil(duration / 60s))` per job. A five-second job still costs a minute.
</Callout>

### The `plan` block

Every response carries a top-level `plan` describing the caller's billing state, which shapes both the window and the human chrome on any `--group-by` axis:

| Field                                      | Meaning                                                                         |
| ------------------------------------------ | ------------------------------------------------------------------------------- |
| `status`                                   | `paid` · `free` · `blocked`. Paid orgs get no bar or banner.                    |
| `period_source`                            | `stripe` · `install_cycle` · `calendar_month` · `explicit` — labels the window. |
| `free_minutes_limit` / `free_minutes_used` | Free orgs only — the remaining lifetime grant, drawn as a bar.                  |
| `blocked_reason`                           | Blocked orgs only — why jobs will not dispatch, shown as a red banner.          |

Fields that do not apply are **omitted, not `null`**. Machine mode passes the whole block through verbatim.

**Exit codes:** `0` ok · `2` usage error · `4` not signed in · `5` control-plane failure. Calls [`GET /api/usage`](/api/usage/get-usage).

## `sling top`

```text
sling top [--by <axis>] [--metric <col>] [--asc | --desc] [--org | --repo]
          [-n <count>] [window] [--json | --agent]
```

Ranks **what burns the most runner time and money** — the hotspots worth optimizing. Groups the same facts as `usage`, but sorts by a chosen metric and adds a period-over-period trend so a regression stands out.

| Flag                | Meaning                                                                             |
| ------------------- | ----------------------------------------------------------------------------------- |
| `--by`              | `workflow` (default) · `job` · `label` · `repo` · `branch`                          |
| `--metric`          | `runner-minutes` · `cost` · `jobs` · `p95-duration` · `p99-duration` · `queue-wait` |
| `--asc` / `--desc`  | Sort direction, default `--desc`.                                                   |
| `--org`, `--repo`   | Scope; mutually exclusive.                                                          |
| `-n`, `--limit`     | Row count.                                                                          |
| `--json`, `--agent` | Machine mode — ranked rows plus a `local` block.                                    |

```console
$ sling top --by workflow --metric cost --window 30d
sling top — by workflow · cost · last 30d

KEY · WORKFLOW   REPO           COST     TREND
Prebuild         acme/api    $528.35    ↑55.2%
Test             (multiple)  $203.11    ↓12.3%
Lint             acme/web     $44.90        —
```

`(multiple)` marks a key spanning several repos. **TREND** compares against the prior equal-length window, or shows `—` when there is no prior data. Calls [`GET /api/top`](/api/top/get-top).

## `sling time`

```text
sling time <run|job|attempt id | URL>
sling time --repo <owner/name> [window]
```

Decomposes CI **wall-clock into phases** — so you can see whether time goes to infrastructure or to your workload. Polymorphic, with a different analysis at each level:

* A **job or attempt** splits wall-clock into the phase enum, with a per-step breakdown inside `steps`.
* A **run** adds DAG analysis: the **critical path**, **parallelism efficiency** (busy-runner-seconds ÷ wall-clock × width), and the **blocking job** — the single job whose speedup most reduces run wall-clock, so optimization effort lands where it pays.
* **`--repo`** aggregates p50/p95 per phase, per runner label, over a window — answering whether a repo's time goes to queue, provisioning, cache, or actual work.

| Flag                                     | Meaning                                                                                                          |
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `<id \| URL>`                            | A run, job, or attempt id, or a pasted GitHub Actions URL.                                                       |
| `--repo <owner/name>`                    | The repo aggregate instead of a single target.                                                                   |
| `--window`, `--month`, `--from` / `--to` | **Only with `--repo`.** Passing one alongside an id is a usage error — a single target already names its window. |
| `--json`, `--agent`                      | Machine mode.                                                                                                    |

```console copy="sling time"
$ sling time 93165914090
Job Name: Lint, typecheck, test & spell · Wall-Clock: 58s · Queue-Wait: 2s

  PHASES                           TIME %-SHARE  TIMELINE
  provision                        4.0s   7.14%  ██░░░░░░░░░░░░░░░░░░░░░░
  checkout+patch                   2.0s   3.57%  ░░█░░░░░░░░░░░░░░░░░░░░░
  steps                           46.0s  82.14%  ░░░███████████████████░░
    Set up Bun                     1.0s          ░░░█░░░░░░░░░░░░░░░░░░░░
    Set up mise tools              2.0s          ░░░█░░░░░░░░░░░░░░░░░░░░
    Install dependencies           1.0s          ░░░░█░░░░░░░░░░░░░░░░░░░
    Lint (Biome)                   2.0s          ░░░░█░░░░░░░░░░░░░░░░░░░
    Lint shell (shellcheck)       14.0s          ░░░░░██████░░░░░░░░░░░░░
    Lint Dockerfiles (hadolint)    0.0s          ░░░░░░░░░░░█░░░░░░░░░░░░
  ▸ Typecheck                     18.0s          ░░░░░░░░░░░████████░░░░░
    Test                           7.0s          ░░░░░░░░░░░░░░░░░░░███░░
    Catalog drift                  1.0s          ░░░░░░░░░░░░░░░░░░░░░░█░
    Spell (typos)                  0.0s          ░░░░░░░░░░░░░░░░░░░░░░█░
  teardown                         4.0s   7.14%  ░░░░░░░░░░░░░░░░░░░░░░██

Top Steps

  STEP                       TIME
  Typecheck                 18.0s
  Lint shell (shellcheck)   14.0s
  Test                       7.0s
  Set up mise tools          2.0s
  Lint (Biome)               2.0s
```

The **TIMELINE** column places each phase and step on the job's wall-clock, so you can see what ran when and what overlapped — not just what was slow. `▸` marks the single largest contributor. **Top Steps** then ranks the steps outright, which is the list to optimize from: here `Typecheck` and `Lint shell` are 32 of the 46 seconds inside `steps`.

Phases come from a frozen vocabulary shared by the runner hooks and the facts store: `queue_wait`, `provision`, `image_pull`, `cache_restore`, `checkout+patch`, `steps`, `cache_save`, `teardown`. Only the phases with time recorded against them are shown. Percentages are share of phase time, so they sum across the phase rows — the nested steps are a breakdown of `steps`, not extra rows in that sum.

**Exit codes:** as elsewhere, plus `6` for partial — telemetry was incomplete, and the body is still emitted. Calls [`GET /api/time`](/api/time/get-time).

## `sling why`

```text
sling why <run | job | attempt id | URL> [--json | --agent]
```

A **server-authored diagnosis** — why a CI job failed, so an agent can fix the failure instead of re-deriving it from logs. It is read straight from the facts store: the last failing step, a bounded `##[error]` window, and run context. **No LLM sits in the request path**, so the same job always classifies the same way. A run id diagnoses that run's most-diagnosable job.

Every diagnosis carries a classification, evidence references, suggested actions with ready-to-run commands, and an agent-ready remediation `prompt`.

Classifications: `step_failure` · `hang` · `oom` · `timeout` · `cancelled` · `terminated` · `infra` · `network_egress` · `unknown`.

On a **cancelled** target, `why` identifies the canceller — a user, a concurrency group, or a merge-queue flush — so an external cancellation is not misdiagnosed as a code failure.

```console
$ sling why job_88886665361
JobID: 88886665361 · JobName: test (3)
Failure Reason: The step 8 "Test" step failed (exit code 101).

Evidence
  Classification: step_failure
  Phase: step 8 "Test"
  Logs:
    ##[error]Process completed with exit code 101

Suggestions:
  1. Verify the logs
     sling logs 88886665361 --grep '##\[error\]'
```

Every diagnosis carries evidence, a suggested next step, and a ready-to-run `prompt` for a downstream agent. Calls [`GET /api/why`](/api/why).

## `sling labels list`

```text
sling labels list [--json | --agent]
```

The catalog of available runner labels and their specs, so `runs-on` mapping and cost hints reference concrete options instead of hardcoded names. The catalog is **static and global** — identical for every caller — so the usual `--org`, `--repo`, and window flags are accepted but inert. `list` is the only subcommand.

```console
$ sling labels list
StarSling runner labels

LABEL                        CPU   MEM (GiB)  ARCH      $/MIN
starsling-ubuntu-24.04         4          16  x64      $0.008
starsling-ubuntu-24.04-2       2           8  x64      $0.004
starsling-ubuntu-24.04-8       8          32  x64      $0.016
starsling-ubuntu-24.04-16     16          64  x64      $0.032
starsling-ubuntu-24.04-32     32         128  x64      $0.064
starsling-ubuntu-24.04-64     64         256  x64      $0.128
```

| Field               | Meaning                                                                    |
| ------------------- | -------------------------------------------------------------------------- |
| `label`             | The `runs-on` runner label.                                                |
| `cpu`               | vCPU count.                                                                |
| `memory_gb`         | RAM in GiB — a strict 4 GiB per vCPU.                                      |
| `arch`              | CPU architecture; `x64` today.                                             |
| `price_per_min_usd` | Per-minute cost, `cpu × $0.002` — the same rate `usage` and `bill` charge. |

See [Instance types](/runners/instance-types) for the same catalog in context. Calls [`GET /api/labels`](/api/labels/get-labels).
