# Configuration (/sling-cli/configuration)



## Environment variables

`sling` is configured entirely through the environment — there are no config files beyond the credential it writes at `~/.config/sling/credentials`.

| Variable     | Meaning                 |
| ------------ | ----------------------- |
| `SLING_HOST` | Control-plane base URL. |

### `SLING_HOST`

Env-only by design — there is no `--host` flag. It must be `https://` (or `http://` on a loopback development host) so the token never travels in cleartext.

## How agents authenticate

**On your own machine, an agent needs nothing extra.** [`sling login`](https://docs.starsling.dev/sling-cli/commands/auth#sling-login) writes the session to `~/.config/sling/credentials` for your user, so any agent running as you picks it up. Sign in once and the agent is authenticated.

See [API credentials](https://docs.starsling.dev/api/credentials) for the credentials the control plane accepts and the scopes each endpoint needs.

## Context resolution

`sling` resolves the org and repo it is acting on so that in-repo invocations need zero addressing:

* **Org** is auto-resolved when unambiguous, overridable with `--org <slug>`, and persisted as a default by [`sling org switch`](https://docs.starsling.dev/sling-cli/commands/auth#sling-org-switch). Multi-org accounts do not pay a flag tax on every call.
* **Repo** is detected from the git remote of the current directory, overridable with `--repo <owner/name>`.

<Callout type="info">
  This is a CLI convenience. The HTTP API never infers either — [`org` is a required parameter](https://docs.starsling.dev/api/credentials#org-scoping) on every org-scoped endpoint.
</Callout>

## Global flags

Available on the root command and every subcommand.

| Flag                  | Meaning                                                                                |
| --------------------- | -------------------------------------------------------------------------------------- |
| `--org <slug>`        | Org context. Auto-resolved when unambiguous; persisted default via `sling org switch`. |
| `--repo <owner/name>` | Repo context. Defaults to the git remote detected in the current directory.            |
| `--version`, `-v`     | Print the version and exit.                                                            |
| `--help`, `-h`        | Show help. An unknown command still fails, so a typo does not read as help.            |

Machine mode is one switch with individually available parts:

| Flag      | Meaning                                                                                                                           |
| --------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `--agent` | Machine mode. Exactly equivalent to `--json --compact --no-input --no-color --yes`. This is the flag machine callers should pass. |
| `--json`  | JSON output on stdout.                                                                                                            |
| `--yes`   | Assume "yes" for confirmations.                                                                                                   |

<Callout type="warn">
  `sling --help` currently lists only `--json`, `--help`, and `--version` in its flags section — `--agent`, `--compact`, `--no-input`, `--no-color`, and `--yes` all work (confirmed by running each against `sling whoami`) but are not shown. This page is the only place `--agent` is documented today; the CLI owner should add these to `sling --help`.
</Callout>

## Output streams

**stdout carries data only.** Human chrome — summary rows, spinners, prompts — goes to **stderr**. This happens unconditionally: the CLI does not check whether stderr is a TTY, so the same spinner and cursor-control sequences land on stderr even when it is redirected to a file or piped into another process. Don't rely on TTY detection to decide whether chrome is present — pipe stderr to `/dev/null` if you need to suppress it silently. That still leaves the more important case: on a prompt-bearing command like [`sling logout`](https://docs.starsling.dev/sling-cli/commands/auth#sling-logout), `--json` by itself only changes what stdout serializes as — it does **not** imply `--yes`/`--no-input`, so a script passing `--json` alone still blocks on the confirmation prompt. `--agent` is the flag that guarantees non-interactive behavior, since it bundles `--yes` and `--no-input`; reach for `--agent`, not bare `--json`, in anything unattended. A failed command usually writes nothing to stdout: a bad id makes `sling runs show <bad-id> --agent` exit `3` with zero bytes on stdout. `sling logs` is the exception — asked for a real job that stores no logs it exits `3` *and* emits its envelope (`{ "lines": [], "has_more": false, "local": { ... } }`). Branch on `$?`, not on whether stdout is empty.

That is what makes `sling usage --json | jq` safe to pipe unconditionally: on failure `jq` receives empty input rather than half a table.

**JSON casing is set per endpoint, not by whether the field crossed the network.** Nearly every control-plane response `sling` passes through — `top`, `labels`, `usage`, `bill`, `runs`, `jobs`, `resolve`, `why`, `time` — is `snake_case` and frozen, matching its API schema. [`GET /api/whoami`](https://docs.starsling.dev/api/identity/get-whoami) is the confirmed exception: it is a real control-plane call like any other (a bad `SLING_HOST` makes `sling whoami --agent` fail with exit `5`, the same as any other command that reaches the API), but its schema is `camelCase` end-to-end (`userId`, `githubLogin`, `expiresAt`). The `local` envelope described below is composed by the CLI rather than passed through, but every key observed inside it so far is a single lowercase word (`org`, `kind`, `slug`, `plan`), so its casing convention for a multi-word key is not yet established by any shipped response. Don't infer a command's casing from "was this local" — check that command's reference page, or default to expecting `snake_case` and treat `whoami` as the documented exception.

The control-plane response, when there is one, is passed through **verbatim at the top level** of the JSON, with a sibling `local` key recording what the CLI resolved (org, repo, etc.) — not nested inside a wrapper. `local` is not on every response: `sling top`, `sling usage`, `sling runs list` and `sling whoami` all carry it, while `sling labels list` returns `{ "labels": [ ... ] }` with no `local` key at all, so treat it as optional when parsing. For example (values redacted):

```json title="sling top --agent"
{
  "by": "workflow",
  "metric": "runner-minutes",
  "window": { "from": "2026-07-25T22:47:14.510Z", "to": "2026-08-24T22:47:14.510Z" },
  "rows": [
    { "key": "ci", "repo": "example-repo", "runner_minutes": 69559, "cost_usd": 556.47, "p50_ms": 69000, "p95_ms": 180000, "p99_ms": 358540, "queue_wait_ms": 14000, "trend_pct": 170.7 }
  ],
  "local": { "org": "example-org" }
}
```

```json title="sling whoami --agent"
{
  "identity": { "userId": "example-user-id", "name": "Example User", "email": "user@example.com", "githubLogin": "example-login" },
  "credential": { "type": "session", "expiresAt": "2026-08-31T20:38:25.617Z" },
  "local": { "org": { "kind": "set", "slug": "example-org", "plan": "paid" } }
}
```

Note the casing split across these two commands: `runner_minutes`, `cost_usd`, and `p50_ms` (the `sling top` control-plane fields) are `snake_case`, while `userId`, `githubLogin`, and `expiresAt` (the `sling whoami` control-plane fields) are `camelCase` — `whoami` calls the control plane the same as `top` does, its response schema is just `camelCase`. In both examples, the `local` block is `camelCase`-shaped regardless of the surrounding response's casing.

[`sling logs`](https://docs.starsling.dev/sling-cli/commands/inspect#sling-logs) differs by mode, and the difference runs the opposite way to the rest of this page. In **human** mode it streams raw log lines to stdout, so `| head` and `| less` behave as you would expect, exiting cleanly when the pipe closes early. Under `--agent` or `--json` it returns a structured envelope like every other command — `{ "lines": [ ... ], "has_more": ..., "local": { ... } }`, one object per line — so a machine caller can hand `logs` to the same JSON parser it uses everywhere else rather than special-casing it.

## Exit codes

Every command exits from one table, so a script or agent can branch on `$?` without parsing output.

| Code | Meaning                                                                                             |
| ---- | --------------------------------------------------------------------------------------------------- |
| `0`  | Success.                                                                                            |
| `1`  | Unexpected CLI or internal error — reserved for a crash, never a mapped API outcome.                |
| `2`  | Usage — bad flags, a prompt refused under `--no-input` or `--agent`, or org ambiguity.              |
| `3`  | Not found — a resolved id has no such run, job, or attempt in this org, or it stores no logs.       |
| `4`  | Auth — missing, expired, or under-scoped credential. The message includes `sling login`.            |
| `5`  | Control-plane or API error — a `5xx`, or a transport failure.                                       |
| `6`  | Partial — telemetry incomplete; the result is still emitted. Used by `sling time` and `sling why`.  |
| `7`  | Rate limited — the control plane returned HTTP `429`.                                               |
| `10` | Remote outcome failed — `sling doctor` unhealthy, or `sling runs show --wait` on a non-success run. |

Codes `3`, `4`, `5`, and `7` are mapped by a shared error handler, so any command can surface them. See [API errors](https://docs.starsling.dev/api/errors) for the HTTP responses behind each.

<Callout type="warn">
  The binary's own `sling exit-codes` help text (as of v0.1.2) documents only codes `0`–`5` — it omits `6`, `7`, and `10`, which are the codes this page documents for `sling time`/`sling why` (partial), rate limiting, and `sling doctor`/`sling runs show --wait` (remote outcome failed). This repo's git history for this page has no earlier version to check when `6`, `7`, and `10` were introduced, so it's unclear whether they're newer than the installed binary's help text or the help text is simply stale. This table is kept as-is (all nine codes) rather than trimmed to match the binary — a healthy local environment couldn't be made to exercise `10` to confirm it directly, but the table's own example below documents it as the unhealthy-`sling doctor` code, and neither this page nor `sling exit-codes` should be trusted blind until the CLI owner reconciles the two.
</Callout>

<Callout type="info">
  Exit `10` is not an error. It means the command succeeded and the *answer* was a failure — which is exactly what lets `sling runs show --wait` gate a pipeline on a run's conclusion.
</Callout>

Read a command's exit code with `$?` immediately after it, since the next command overwrites it:

```console
$ sling doctor; echo $?
10   # 0 = healthy, 10 = unhealthy
```
