Manage Runners Logo
Manage Runners
Documentation menu

The CLI for AI agents and automation

Agent mode, command discovery, the JSON contract, exit and error codes, confirmations, scopes, and the embedded agent skill.

This page is the contract for AI agents and scripts that operate Manage Runners through manage-runners. It is also available as Markdown, and the whole documentation as one file at /llms-full.txt.

Roles: the human sets up, the agent operates

A human installs the CLI, logs in and decides what the agent may do:

  1. Install the CLI.
  2. Give the agent a token, either:
    • interactive login with manage-runners setup or auth login, which yields a read-only token for 30 days, plus write scopes only if the human adds them with --scope, or
    • a dashboard token with exactly the scopes and expiry the task needs, provided to the agent through its secret store. See Personal access tokens.
  3. Optionally install the agent skill.

The agent then runs commands with --agent, reads the results as JSON, and asks the human before anything that costs money or deletes data.

Agent mode

manage-runners runner list --agent

--agent means JSON output and no prompts. It is the same as --output json --non-interactive. In agent mode, a command that would need an answer fails instead of waiting for one.

Discover commands

manage-runners commands --agent
manage-runners help runner delete --agent

commands returns the catalog of every command. Each entry has:

Field Meaning
name Dotted name, for example runner.delete
path The words to type after manage-runners, for example ["runner", "delete"]
summary One-line description
mutation true if the command changes local configuration or remote state
destructive true if it deletes or stops something
paid_resource_effect create, recreate, delete or null: what it does to paid Hetzner resources
required_scopes Token scopes the command needs
required_flags Flags that must be given
confirmation paid_resource, runner_id or null, see Confirmations
supports_json Whether JSON output is supported
supports_non_interactive false for commands that need a human at a terminal, such as setup

help <command> --agent returns the same entry plus every flag with its usage text and whether it is required. The command reference shows the catalog and the help text of every command.

Output

Success:

{
  "ok": true,
  "command": "runner.get",
  "data": {
    "profile": "default",
    "origin": "https://api.managerunners.com",
    "runner": { "runnerId": "812999368881481087", "name": "build-01", "state": "READY" }
  },
  "meta": { "schema_version": "1", "cli_version": "1.0.0", "api_version": "1", "request_id": "deploy-42" },
  "warnings": []
}

Failure:

{
  "ok": false,
  "command": "runner.delete",
  "error": {
    "type": "usage",
    "code": "USAGE",
    "message": "--yes is required in non-interactive mode",
    "retryable": false,
    "unknown_outcome": false,
    "http_status": null,
    "field_errors": [],
    "details": {}
  },
  "meta": { "schema_version": "1", "cli_version": "1.0.0" },
  "warnings": []
}

The examples are abbreviated: the shape of data depends on the command, so read it from a real response. Both results and errors are written to standard output; prompts and nothing else go to standard error. Always check the exit code and ok.

  • --request-id ID sends a request ID (1 to 128 safe characters) to the API and echoes it in meta.request_id, which helps to correlate logs.
  • --output jsonl writes one JSON line per item of a list, and one line {"type": "warning", ...} per warning. An empty list writes nothing.
  • IDs of runners and organizations are decimal strings. Keep them as strings; they do not fit into a double-precision number.

Exit codes

Code Meaning What to do
0 Success
2 Invalid command, flag or value, or a missing confirmation Fix the command. Nothing was sent.
3 Token missing, invalid or expired Ask the human to log in again.
4 Missing scope, role or plan Do not retry. See the error code.
5 Runner, schedule or organization not found, or not monitored Check the ID and organization.
6 The runner’s state does not allow the action, or a name is ambiguous Read the runner’s state first.
7 The API rejected the input, or waiting timed out after the change was accepted Fix the input, or check the runner’s state.
8 Rate limited or temporarily unavailable Retry later with backoff.
9 The API could not be reached Check connectivity. Before repeating a change, check whether it happened.
10 A change may or may not have happened Do not retry. Read the state and reconcile first.
11 auth logout --revoke could not confirm revocation or local removal Check the token in the dashboard.
12 Configuration or credential store problem Run manage-runners doctor.
13 Unexpected API response, too large a response, or an unsupported version; for self-update, a release or download that failed its checks Update the CLI; for metrics, use a shorter range. Do not retry a rejected download.
14 version --check: an update is available (ok is true) Tell the human; update only with their consent.
70 Internal error Report it.

Error codes

The most important values of error.code:

Code Exit Meaning
USAGE 2 Invalid input or missing confirmation
ORGANIZATION_AMBIGUOUS 2 --org matches several organizations by name
UNAUTHENTICATED 3 No valid token
FORBIDDEN 4 The token lacks a scope, or the plan does not include the feature
SUBSCRIPTION_REQUIRED 4 Monitoring needs an active plan. The same HTTP 403 is used for a missing scope, so this is the likely, not certain, cause.
ORGANIZATION_PERMISSION_DENIED 4 Your role in the organization does not allow the action
NOT_RELEASE_BUILD 2 A development or modified build cannot run self-update; error.details.hint names the install script (Linux, macOS) or the manual download steps (Windows)
NOT_FOUND 5 The runner or schedule does not exist in the selected organization
ORGANIZATION_NOT_FOUND 5 The organization is not among your memberships
NOT_MONITORED 5 The runner exists but is not active or has monitoring off
RELEASE_NOT_FOUND 5 The release given to self-update --version does not exist
CONFLICT 6 The runner’s state does not allow the action
AMBIGUOUS 6 Several runners have that name; use --id
VALIDATION 7 The API rejected the input
TIMEOUT 7 Waiting for a runner timed out; the change itself was accepted
RETRYABLE 8 Try again later
NETWORK 9 Network error
UNKNOWN_OUTCOME 10 The change may have happened; reconcile before retrying
REVOCATION_UNCONFIRMED 11 Token revocation could not be confirmed
CONFIG, CREDENTIAL_STORE 12 Local configuration or credential store problem
INSTALL_DIR_NOT_WRITABLE, UPDATE_FAILED 12 self-update cannot write or replace the binary, or the binary changed while it ran; when the directory is not writable, error.details.hint names the install script (Linux, macOS) or the manual download steps (Windows)
INCOMPATIBLE_RESPONSE, RESPONSE_TOO_LARGE 13 The API response or release manifest could not be used
PLATFORM_NOT_RELEASED 13 The release has no build for this operating system and CPU
DOWNLOAD_REJECTED 13 The download failed its size, SHA-256 or archive checks, or was redirected to another host; nothing was changed

Warnings in warnings do not change the exit code. Examples: TOKEN_ENV and TOKEN_ENV_OVERRIDES_STORE (a token came from the environment), RANGE_EXCEEDS_HISTORY (metrics history is shorter than the range), RUNNER_WARNING (a message from the API about the runner).

Confirmations

In agent mode the CLI never prompts. Commands that would ask a human must be confirmed with flags, and the agent may only add these flags when the human has approved the specific action.

Catalog confirmation Commands Non-interactive confirmation
paid_resource runner create, runner resume, runner duplicate, runner pause, and runner update when it recreates the server --yes
runner_id runner delete, runner forget --yes and --confirm <runner-id>, exactly equal to --id
runner_id schedule delete --yes and --confirm <runner-id>, exactly equal to --runner-id
self_update self-update --yes
null every other command none

runner update needs --yes only with --product-id, --gitlab-host, --runner-token-env, --concurrency, --executor, --ssh-key-id or --monitoring, because those recreate an active runner’s server. --name, --label and --hetzner-token-env alone do not.

A missing confirmation fails with exit code 2 before anything is sent. self-update reads the release manifest first, so it can report that nothing needs to change, but downloads nothing without confirmation.

Never run self-update unless the human asked for it or agreed to it; version --check is safe to run.

Treat every command with mutation: true as potentially billable, and check paid_resource_effect:

  • create: runner create, runner resume and runner duplicate create Hetzner servers, which Hetzner bills to the user.
  • recreate: runner update may delete and recreate an active runner’s server, interrupting its jobs.
  • delete: runner pause and runner delete delete servers. runner delete also deletes the IP addresses and the schedule.

runner forget only removes an UNKNOWN runner’s record and leaves the server untouched. It is never a substitute for runner delete.

Scopes

Scope Commands
profile:read auth status, profile show, org list, org use, resolving --org
runners:read runner list, get, watch, wait, metrics, ssh-keys, update, and --wait on any runner change
runners:write runner create, update, pause, resume, duplicate
runners:delete runner delete
runners:forget runner forget
schedules:read schedule get
schedules:write schedule set, enable, disable, delete
products:read product list, get, locations
providers:ssh-keys:read provider hetzner ssh-keys
subscriptions:read subscription show, subscription plans

runner update needs both runners:read and runners:write, because it reads the runner before writing the merged settings. --wait on create, update, pause, resume or duplicate polls the runner and needs runners:read as well. If polling fails, for example with exit code 4 for a missing scope or 7 for a timeout, the change itself was already accepted and is not undone: read the runner before doing anything else.

The token’s scopes and the member’s role both apply. A token cannot manage billing, organizations, members, invitations or other tokens. Imported tokens keep the scopes they were created with; the CLI cannot add scopes to them.

Credentials for agents

  • Prefer a token passed through the environment: MANAGE_RUNNERS_TOKEN, or --token-env NAME for a variable with another name. Nothing is stored.
  • auth status --agent reports where the token comes from in credential.source (environment, keyring or file). It never prints the token.
  • Never print a token, pass it as a literal argument, or write it into files or logs.
  • Use the file credential store (--credential-store file) only when the human agreed to it.
  • Do not change profiles you were not asked to use.

Idempotency and unknown outcomes

runner create and runner duplicate send an idempotency key. The CLI generates a random key for each invocation unless you pass --idempotency-key (16 to 128 letters, digits, ., _ or -). The API deduplicates a request only when the user, organization, route, key and request are all the same. The 24 hours below count from the first attempt with that key, not from when it finished:

  • Completed first attempt, retried within 24 hours of the first attempt: the API returns the original result and creates nothing new.
  • Completed first attempt, retried later than that: the key is forgotten, and the same request creates another runner.
  • First attempt not completed (still running, or interrupted before its result was stored): the API rejects every retry with that key as a conflict (exit code 6), also after 24 hours. Read the state instead of retrying.
  • Same key, different request: the API rejects it as a conflict (exit code 6).

So reusing a key only protects a retry made soon after the first attempt, by the same user in the same organization, with the same flags. Never rely on an old key: before retrying a create or duplicate, list the runners and check whether the first attempt already created one.

When a change ends with exit code 10 (UNKNOWN_OUTCOME), the change may have happened. error.details may contain retryGuidance and idempotencyKey. Read the runner list or the runner, decide what actually happened, and only then continue.

Organizations

Without --org or a stored organization, commands use the user’s default organization. Use org list --agent to see memberships and roles, and --org ID_OR_NAME for one invocation. Store an organization with org use only when asked. See Profiles and organizations.

The agent skill

The CLI embeds a skill file that tells agents how to use it safely. Install it for your agent:

manage-runners setup skill install --target claude     # ~/.claude/skills/manage-runners
manage-runners setup skill install --target codex      # ~/.codex/skills/manage-runners
manage-runners setup skill install --target opencode   # opencode skills directory in your user config directory
manage-runners setup skill install --path ./skills/manage-runners

setup skill status reports whether it is installed and unchanged, and setup skill remove removes an unmodified copy. --dry-run shows what would change. An existing file that the CLI did not install, or a modified copy, is only replaced with --force.

This is the skill embedded in manage-runners (skill version 1):

---
name: manage-runners
description: Operate manage-runners safely through the official CLI.
metadata:
  owner: manage-runners
  version: "1"
---

# Manage Runners

Use `manage-runners commands --agent` to discover supported operations and required scopes.

Interactive `auth login` and `setup` request `profile:read`, `runners:read`, `schedules:read`, `products:read`, `providers:ssh-keys:read`, and `subscriptions:read` by default. Add write, delete, or forget scopes only when explicitly authorized. PAT imports keep the scopes granted by the server.

When a keyring is unavailable, a set `MANAGE_RUNNERS_TOKEN` is used automatically as the PAT for read and runner commands; `--token-env ENV_NAME` overrides it for one invocation. `auth status --agent` reports the credential source. Never print either variable's value; `auth login --token-env` instead imports a PAT into the credential store.

To store a credential without a keyring, pass `--credential-store file` or set `MANAGE_RUNNERS_CREDENTIAL_STORE=file`. The CLI keeps only a scoped, expiring PAT in a 0600 file, never the password or 2FA secret; tell the user so, and that it can be revoked in the dashboard or with `auth logout --revoke`. Never select the file store without the user's agreement.

Always use `--agent` for automation. Treat mutation commands as potentially billable and require the caller to supply every confirmation requested by command discovery.

`runner update` with `--product-id`, `--gitlab-host`, `--runner-token-env`, `--concurrency`, `--executor`, `--ssh-key-id` or `--monitoring` deletes and recreates an active runner's VM and needs `--yes`; `--name`, `--label` and `--hetzner-token-env` alone do not.

Use `runner metrics --agent` (optionally `--id`/`--name` and `--range 1h|24h|7d|30d|90d`) to read resource monitoring. `SUBSCRIPTION_REQUIRED` means monitoring needs an active paid subscription; do not retry. Only change monitoring when explicitly asked.

Runners, schedules, metrics and the subscription belong to an organization. Without `--org` and a stored organization, commands use the caller's default organization. Use `org list --agent` to see memberships and roles, `--org ID_OR_NAME` for one invocation, or `org use ID_OR_NAME` (`org use --clear`) to store one in the profile only when asked. `ORGANIZATION_PERMISSION_DENIED` means the member's role does not allow the action; do not retry. `ORGANIZATION_NOT_FOUND` means the organization is not among the caller's memberships.

`version --check --agent` reports whether a newer CLI release exists and exits 14 when one does; it is safe to run. Never run `self-update` without the user's consent, and pass `--yes` only after they agreed to that update.

Never print credentials, pass a token as a literal command argument, or change an unrelated manage-runners profile.

Machine-readable documentation

  • Every page of these docs is available as Markdown: add index.md to the page URL, for example this page as Markdown.
  • /llms.txt lists all pages with their Markdown URLs.
  • /llms-full.txt contains the whole documentation, including the command reference, in one file.