About sling
sling is StarSling's agent-oriented CLI for querying and analyzing live GitHub Actions data. It is read-only and returns structured JSON; its skill also routes requests and describes troubleshooting paths.
Key features
- 01Query run, job, and attempt data
- 02Inspect account runner-minutes
- 03Return structured JSON
- 04Use stable exit codes
- 05Read and analyze CI data only
Use cases
- 01Diagnose a GitHub Actions run or job
- 02Inspect CI logs and outcomes
- 03Review account runner-minute usage
- 04Fall back to gh for reads when sling is unavailable
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ starslingdev/ skills --skill slingAfter installing, type/slingin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
CHANGELOG.mdLICENSEevals/evals.jsonprompt-routing.jsonreferences/command-reference.mdcommand-surface.jsongh-fallback.mdtests/test_sling_routing.pyDetails
From the project READMEsling CLI
sling is StarSling's agent-first CLI for GitHub Actions. It answers
questions about live CI data — one run, one job, one attempt, or an
account's runner-minutes — from StarSling's control plane, with structured
JSON on stdout and a stable exit-code table. In this release it is
read-only: it reads and analyzes, and there is no sling command that
changes anything in GitHub.
This skill's job is picking the right tool per request. Live question
about a run, a job, or spend → sling. Anything that changes CI state →
gh. A repo-wide audit of workflow files → hand off to the audit skill
that owns it and stop. Do not invent a sling subcommand for something
sling does not do; the command
surface is the complete list.
Every fact in this skill's command reference was read off sling itself
(v0.1.8) rather than from documentation — see
references/command-reference.md for the
verified per-command JSON shapes.
Preflight: is sling installed and authenticated?
Do this once per session, before the first sling command.
-
Is it on PATH?
command -v sling. If it is missing,slingis installed by a one-line installer published on the installation page athttps://docs.starsling.dev/sling-cli/installation— point the user at that page and let them run it, rather than pasting an installer command from memory. It supports Apple Silicon macOS and x64 glibc Linux only; there is no Windows build, so on Windows say so plainly and use thegh-only path in step 6. -
Is the environment healthy?
sling doctor --agent. It emits{"checks": [{"key", "ok", "detail", ...}]}on stdout and exits10when a real check fails (0when healthy). The checks name the problem. Seven of the eight are actionable —token,control_plane,clock_skew,git_remote,patch_tooling,version,org— so act on the one that isok: falseinstead of guessing (the eighth,agent_skill, is advisory only; see below) — and when SEVERAL areok: false, act oncontrol_planefirst: an unreachable control plane failstokenandorgas dependents, and thetokencheck still attachesfix_command: "sling login"— a wrong lead during an outage, since the device flow cannot reach the control plane either. Network first, never login."skipped": truecan also mean "not checked" for exactly this reason — readdetail, not just the flag. Aversioncheck withwarn: trueis an upgrade notice, not a failure — anyfix_commandit carries is the installer one-liner, a shell pipeline, so surface it to the user rather than running it yourself.versionreadsok: false, skipped: truewhen it could not compare at all (installer URL unreachable, served version unreadable) — that is "not checked", not "out of date". Theagent_skillrow is likewise advisory and can never makedoctorunhealthy: a check counts as failing only when it isok: falseAND notskipped, and this row's onlyok: falseshape is alsoskipped: true. It reports whether this skill is installed for a coding agent on the machine, and it can read "not installed" while you are reading the skill, because it only looks in a fixed set of directories. So report the row with its detail and never run itsfix_commandyourself — installing is the user's call (see references/command-reference.md). -
Not authenticated (
doctor'stokencheck fails, or a command exits4whose stderr does not name an org): ask the user to runsling loginin their own terminal, then wait for them to say it is done. Do not run it yourself, and never pass--agentto it.Read the stderr before acting on exit
4.You don't have access to org "<name>"is a missing app installation, not a stale credential, and it belongs to step 5 rather than here. It endsRun \sling login`` anyway, because a generic decorator appends that to every auth-class error — so the code and the advice both point the wrong way. Signing in again succeeds and changes nothing, which reads to the user as a broken login rather than as the missing installation it is.Signing in needs a person at a browser:
sling loginprints a device code, opens a GitHub approval page, and blocks until someone approves it. Launched as a subprocess that is unworkable in both directions — the output arrives only once the command has already finished or timed out, so the code is never visible while it can still be used, and--agentwould refuse the prompt outright with exit2. There is no automated path here, and pretending otherwise just hangs the session.The credential is saved per user at
~/.config/sling/credentials, so once they have signed in, an agent running as them is authenticated with no further setup. Re-runsling doctor --agentto confirm, then carry on. -
Wrong org — you belong to several and have not picked one.
doctor'sorgcheck naming multiple slugs, an exit2about org ambiguity, or an id that resolves to nothing but plausibly exists in another of your orgs:sling org switch <slug>, or pass--org <slug>on the single command.--organd--repo <owner/name>are accepted by most subcommands but not bysling logs, which parses strictly and rejects both with exit2(Unknown flag "--org"). That is the command this routing table reaches for most, so fix a wrong org for a logs read by switching the default rather than by adding a flag to the call.--repootherwise defaults to the git remote of the current directory, and takesowner/name— a bare repo name is rejected with exit2.doctor'sorgcheck also fails when you belong to no orgs, which looks similar and is not this. There is no slug to switch to — that is step 5. -
StarSling gate — the app is not installed anywhere you can see.
slingreports CI that the StarSling GitHub App collected, so a valid login sees nothing until the app is installed on a GitHub organization. Two shapes, one cause, one answer:- An org exists and you cannot see it — exit
4,You don't have access to org "<name>". Rule out a typo first. A slug that could never exist returns byte-identically, so this message alone does not establish that an org is real:sling org switch <slug>answers withUnknown org "<slug>" — your orgs: …, which both settles it and prints the list. Telling someone to install a GitHub App on an organization they mistyped is worse than saying nothing. The URL carve-out below does NOT cover the flag path: a third party's org passed via--repo/--orglands here at exit4(verified:--repo vercel/skills), same split — your own orgs are listed byorg switch; a third party's routes to theghread fallback of step 6. The message endsRun \sling login``, which cannot work: the credential was never the problem. That suffix is appended to every auth-class error by a generic decorator, so read it as boilerplate rather than as advice about this case. - You have no orgs at all — exit
2,You don't belong to any orgs yet., anddoctor'sorgcheck saying the same. This is the ordinary state of someone who installedslingand signed in before ever installing the app; signing in succeeds and gives no hint that a step is missing. Do not read exit2here as a bad flag, and do not send them tosling org switch— there is nothing to switch to.
A sandboxed agent shell can also fail an auth probe for reasons of its own, so retry once with host access before trusting a single failure.
In either shape, STOP and tell the user plainly — every answer this skill gives comes from StarSling's record of their runs, so until the app is installed there is no run data to read and only the
ghread path remains. Give them the path: install the app athttps://github.com/apps/starslingdevon the organization whose CI they are asking about, or ask an owner to add them. Continue on thegh-only path ONLY if they say so, and name what is unavailable when you do.A pasted URL from someone else's org never reaches this gate. The resolver searches only orgs you belong to, so a foreign Actions URL fails with exit
3(not-found), not exit4— verified live on amastra-airun URL. The app-install remedy applies only to orgs the user belongs to; for a third party, the right move is theghread fallback of step 6.Organizations only. The app installs on a personal repository but StarSling does not pick up its jobs, so there is never data to report for one — say that rather than reporting an empty result.
- An org exists and you cannot see it — exit
-
If
slingcannot be installed or authenticated at all, fall back to theghread path for whatever is achievable (gh run view,gh run view --log-failed) and tell the user plainly that the richer diagnosis —sling why's classification,sling time's phase breakdown — is unavailable withoutsling. Do not silently degrade.Run the gh gate below before promising that path: with neither CLI available there is nothing left to fall back to, and saying so up front beats discovering it one failed command at a time.
Always pass --agent
--agent is the machine-mode flag: JSON on stdout, no prompts, no colour.
Pass it on every data command and parse stdout as JSON. Do not also pass
--json — help pages render the pair together (--json, --agent), with
per-command wording.
Do not describe it as an alias for a longer flag list. The published docs
call it "exactly equivalent to --json --compact --no-input --no-color --yes", and on v0.1.8 --compact is not a flag this binary has at all:
passing it changes nothing because unknown flags are ignored, not because it
does anything. Output is pretty-printed, indented JSON either way, so feed
stdout to a real JSON parser and never to a line-oriented one that assumes
one object per line.
Never pass --agent to sling login. Signing in requires a human to
open a browser and approve a device code, and --agent carries
--no-input, which refuses the prompt rather than showing it — turning the
one command that needs a person into exit 2. sling org switch has the
same shape: under --agent its picker is refused, so give it an explicit
slug (sling org switch acme --agent).
-
Branch on the exit code, never on whether stdout looks empty. Human chrome (spinners, summary rows, prompts) goes to stderr, and only when stderr is a TTY.
6and10are outcomes rather than errors: they emit the full JSON payload on stdout with stderr empty — an unhealthysling doctor --agentexits10and still returns every check, which is exactly what the preflight above asks you to read.A non-zero exit does not mean stdout is empty, and a well-formed JSON body does not mean success.
sling logson a job that stores no logs exits3and still prints{"lines": [], "has_more": false, …};sling resolveon an ambiguous id exits2and still prints{"candidates": […]}. So read stdout as JSON and read the exit code, and when the two disagree the exit code decides. Reporting that emptylinesarray as "nothing in the log" is exactly the silent false negative this skill exists to prevent — the reason is on stderr, which stays worth reading even when stdout parsed cleanly. -
Under
--agent, every data command returns JSON,sling logsincluded (sling exit-codesis help text: exit0, empty stdout, prose on stderr). A log-less job's payload carries its own machine discriminator —"local": {"empty": {"kind": "absent"}}— alongside"lines": []. -
JSON keys are
snake_caseon every command exceptwhoami, which returns camelCase (userId,githubLogin,expiresAt). Key a parser per command, not on a local-vs-remote rule.
The traps in this contract are collected under Gotchas — worth reading before the first parse.
The routing table
Decide with two questions, in order.
1. Does the request change anything, or only read? If the user wants to
change CI state — re-run a job, cancel a run, trigger a workflow, flip a
workflow on or off, pull down an artifact — sling cannot do it in this
release. Use gh directly; do not attempt a sling subcommand for these
under any name.
2. If it only reads: is it about one run, one job, or aggregate
cost/usage — or about the repo's CI configuration as a whole? One run,
one job, or spend attribution → sling. The repo's workflow YAML, its
best-practice adherence, its security posture, or a multi-run optimization
sweep → hand off (see Handoff) instead of
reading YAML yourself.
| User is asking about... | Route to | Command |
|---|---|---|
| Why a specific job failed | sling | sling why <run|job|attempt id|URL> |
| Where a run's wall-clock went (queue wait vs. provision vs. steps vs. teardown) | sling | sling time <run|job|attempt id|URL> |
| The same, as percentiles across a repo | sling | sling time --repo <owner/name> --window <n>d |
| Recent runs and their status | sling | sling runs list [--branch --status --conclusion --trigger --workflow-path --label --window --limit] |
| Detail on one run (its jobs, attempts, runner) | sling | sling runs show <run id|URL> [--wait] [--fail-fast] |
| Which jobs in a run failed, or jobs across a repo | sling | sling jobs list --run <id> / --repo <owner/name> |
| Which step in a job failed, and each step's duration | sling | sling jobs show <job id|URL> |
| Only the log lines that matter for a failure | sling | sling logs <run|job|attempt id|URL> [--grep <re>] [--since <dur>] [--limit <n>] |
| Turning a pasted Actions URL into a run/job/attempt id | sling | sling resolve <id|URL> [--target run|job|attempt] — a run id covering several jobs is ambiguous: exit 2, {"candidates": […]} on stdout. Pass one of the returned candidate ids (att_<jobid>.<n>) or --target run; --target job/attempt re-state the kind and return the same ambiguity |
| The biggest time or cost hotspots | sling | sling top [--by workflow|job|label|repo|branch] [--metric ...] |
| Runner-minutes and cost attributed per repo/workflow/label/day | sling | sling usage [--group-by <axis>] [--window <n>d] |
| What is owed this period, or past invoices | sling | sling bill, sling bill history |
| Which runner labels/sizes exist and what they cost | sling | sling labels list |
| Identity, org, credential, environment health | sling | sling whoami, sling doctor, sling org switch — and sling login, which the user runs, not you |
| Re-run a run or its failed jobs | gh | gh run rerun <run-id> [--failed] |
| Cancel an in-progress run | gh | gh run cancel <run-id> |
Trigger a workflow (workflow_dispatch) | gh | gh workflow run <workflow> [-f key=value] |
| Enable or disable a workflow | gh | gh workflow enable|disable <workflow> |
| Download an artifact | gh | gh run download <run-id> |
| Delete a run | gh | gh run delete <run-id> |
| Approve or reject a pending deployment | gh | gh api — see references/gh-fallback.md |
| The checks on a specific PR | gh | gh pr checks <pr> — then take the failing run/job id back into sling why / sling time |
| Secrets or repo/environment variables | gh | gh secret, gh variable |
| The contents of a workflow file | neither | Read the .yml directly — this is a file question, not a CLI action |
| A repo-wide grade, speed sweep, or security scan | neither | Hand off — see Handoff |
Ambiguous asks. With no action verb (re-run / cancel / trigger / enable
/ disable / download) and a specific run, job, or spend in view, default to
sling: it is the read path and costs nothing to try. If the ask is
read-only but sling has no matching subcommand — "what does this
workflow's on: trigger include" is YAML content, not run data — read the
file rather than forcing it through either CLI.
Compound asks — "tell me why this run failed, then re-run it" — are two
steps, in order: do the sling half first (sling why), report it,
then do the gh half (gh run rerun) as an explicit, separately announced
action — and run the gh gate (below) before promising that second half: a
working sling says nothing about whether gh is signed in. Never chain
into a state change without telling the user what changed.
Command reference
Full per-command flags and the verified JSON shape of every response: references/command-reference.md. The machine-readable list of every command that exists, which is what keeps this skill from inventing one: references/command-surface.json.
The short version, grouped the way sling --help groups them:
- Auth & setup —
login,logout,whoami,doctor,org switch. - Inspect CI —
runs list,runs show,jobs list,jobs show,logs,resolve. - Diagnose —
why(classification, evidence, suggested actions, and a ready-to-usepromptfield),time(wall-clock split intoqueue_wait,provision,image_pull,cache_restore,checkout+patch,steps,cache_save,teardown). - Cost & capacity —
usage,top,bill,bill history,labels list.
Two habits worth keeping:
sling whyon a RUN (id or URL) can answer for ONE selected job at exit0— verified on a cancelled three-job run, where it diagnosed the single failed job and never mentioned the other two or the cancellation. Before reporting that diagnosis as the cause of a run, confirm the run's job set (jobs list --run <id>) whenever the run has several jobs or did not conclude cleanly.- Start from
whyfor a failure, not fromlogs.whyis classified server-side with no LLM in the loop, and itssuggested_actions[]carry the exact follow-up command (usually asling logs --grep) instead of making you guess a pattern. Itspromptfield is written for an agent to act on directly. sling logsis a filter, not a dump. Reach for--grepand--limitbefore pulling a whole transcript;has_moreplus--cursorpages the rest.
Exit codes
Branch on $?, read immediately after the command. The inference fails in
both directions: a successful result can be empty, and a failed command
can still print a full JSON body — logs exits 3 with {"lines": []},
resolve exits 2 with {"candidates": […]} — and so do logs, why
and time on a bare multi-job run id, with why/time putting nothing on
stderr at all. The invocation was valid: read the candidates off stdout and
pass one id (or --target run) rather than re-reading --help for a usage
error that is not there. Neither an empty stdout nor
a parseable one tells you what happened; only $? does.
| Code | Meaning | What to do |
|---|---|---|
0 | Success | — |
1 | Unexpected internal error (a crash) — or a subcommand that does not exist | Read stderr first. unknown command "<name>" for "sling" is a routing mistake, not a crash: the binary lists its real commands, so correct the name against that list rather than filing a bug — this is where the doctor fix_command gotcha below lands. stderr naming an environment variable (SLING_HOST must be a valid https URL …) is an environment fix, not a bug. Anything else: do not retry blindly, and surface the stderr text as a bug report |
2 | Usage — bad flags, a prompt refused under --agent, org ambiguity, or no orgs at all | Read the stderr first. You don't belong to any orgs yet. → step 5, the app is not installed; never a flag problem. Org-ambiguous → pass --org or sling org switch. Otherwise fix the invocation against sling <cmd> --help |
3 | Not found — no such run/job/attempt in any org you can access, or a real job that stores no logs | A pasted URL from a third-party org lands here, not on exit 4 — the resolver searches your orgs and reports not-found (No run/job/attempt matches that id in an org you can access). For an org the user does not belong to, do not suggest installing the app on it: fall back to the gh read path (public repos answer) and say the richer sling diagnosis is unavailable. Otherwise try sling resolve, confirm the org, then ask the user to confirm the id |
4 | Auth — or no StarSling installation on that org, or a third party's org named via a flag. Read the stderr text before acting | You don't have access to org "<name>" → the app is not installed there (or the user is not a member); point them at https://github.com/apps/starslingdev, do NOT retry login. Anything else → ask the user to run sling login themselves (a browser approval, never --agent), then retry once. If the retry exits 4 again, STOP and report what was verified — never ask for a second login |
5 | Control-plane or API error (5xx or transport) | Retry once after a short backoff (~2s); on a second failure fall back to the gh read equivalent and say sling was unreachable |
6 | Partial — telemetry incomplete, result still emitted (time, why) | Not an error. Use the result, and tell the user it is partial |
7 | Rate limited (HTTP 429) | Back off and retry once. On a second 429, stop: fall back to the gh read equivalent and say sling was rate limited |
10 | Remote outcome failed — doctor unhealthy, or runs show --wait on a run that did not succeed | Not a CLI error. This is the answer: report the unhealthy check, or the run's failure |
sling exit-codes on v0.1.8 prints only 0–5; codes 6, 7, and 10
are real and documented (and doctor --help names 10 itself), so treat
that help text as abridged. Any non-zero code not in this table: surface
stderr to the user rather than guessing a recovery.
Gotchas
These are the places where sling behaves differently from what its own
output, its --help, or its documentation implies. Each was found by
running the binary; none of them announce themselves at runtime. Everything
version-pinned here was verified against sling v0.1.8 and gh 2.93.0 —
re-verify on upgrade.
doctor'sfix_commandis advice, not a verified command. On 0.1.2 itsversioncheck recommended a command that did not exist — it emitted"fix_command": "sling update", andsling updatedoes not exist in any release (0.1.8 still rejects it as unknown; the check now emits the installer one-liner instead). Other checks emit templates with unfilled placeholders:git_remotesuggestsgit remote add origin <github-url>verbatim. Never run afix_commandunchecked. The general lesson outlives the original bug: a command name printed by a tool is not proof the tool has it.- Exit
4does not always mean the credential is stale, and its message misdirects when it does not. An org StarSling was never installed on fails withYou don't have access to org "<name>". Run \sling login`` — the same shape as an expired session, ending in advice that cannot work. Read the message before acting on the code: a named org means the app is missing there, not that the login is. - An empty listing is a coverage hole, never a finding.
sling runs list --repo <name>returns{"runs": []}and exit0both when the repo truly had no runs in the window and when StarSling is not watching that repo at all. The payload cannot tell them apart, so an empty result means this check did NOT run — say so with the reason you cannot rule out, rather than reporting "you have no CI runs" as a fact about their repo. A repo that does not exist at all returns the same empty shape at exit0(verified live), so when the answer matters, a cheapghread —gh repo view,gh run list— settles which of the three it is. The same three-way-ambiguous empty comes back fromjobs list --repo({"jobs": []}),usage --repo({"rows": []}) andtime --repo({"phases": []}— at exit6, whichtime --reporeturns even on full data, so "partial" there carries no signal; the empty phase list is the coverage hole). - There is no client-side timeout. A black-holed control plane (packets
dropped, not refused) hangs
slingindefinitely — verified past 45s — with no exit code and nothing on either stream. Wrap long calls in a timeout, and when one fires report the timeout itself: a killed command produced no result, and "no result" is never "no findings". A refused connection returns exit5cleanly. - Unknown flags are ignored, not rejected.
sling runs list --bogusexits0and returns unfiltered rows. Exit0is therefore not evidence that a filter applied — check the rows you got back before reporting a filtered answer. --helplists an abridged flag set.runs list --helpomits--trigger,--workflow-path,--labeland the window flags, all of which work. Absence from--helpis not absence from the CLI; the fuller list is in references/command-reference.md.sling logsreturns JSON under--agent— the same structured envelope as every other data command. (An earlier revision claimed the published docs disagreed; they do not — the docs document exactly this. The real doc discrepancy is smaller: they call the envelope one-object-per-line, and it is pretty-printed, indented JSON.)- A zero phase in
sling timecan mean "not measured". In v1,image_pullandcold_startare bundled intoprovisionand reported as0with a reason inmeta.truncated. Read that array before telling a user a phase took no time — a measurement gap reported as a finding is a false finding. - Exit
10is an answer, not a failure.doctorunhealthy andruns show --waiton a failed run both exit10. Report what it says. Exit6likewise carries a real result, flagged partial. --agentsilently includes--yes. Harmless whileslingis read-only, but do not treat a confirmation prompt as a safety net.sling runs showgives nojob_id— itsjobs[]carryjob_nameandattempts[]only. Usejobs list --run <id>orresolvewhen you need job ids.sling topcan report a repo as(multiple)when one key spans repos. Do not print that as a repo name without saying what it means.sling billhas two totals.amount_due_usdis what is owed;amount_usdis before credits.status: "open"means the period is still accruing, so it is not a final number.
Handoff to the audit skills
sling reads live run data and has no ability to read workflow
configuration at all, so it structurally cannot audit, grade, or scan a
repo. When a conversation turns into "audit my whole CI setup", "grade my
CI", "why is CI slow generally", or "is this secure", stop and name the
right skill rather than looping sling over every run:
| The ask | The skill |
|---|---|
| A configuration best-practices grade ("grade my CI", "CI score") | ci-score |
| A measured speed / runner-minute audit across many runs ("why is CI slow") | ci-speedup |
| A security scan of the workflow files ("is my CI secure") | ci-secure |
Each reads a local checkout and runs its own catalog. Name the skill and let the user run it; do not approximate one of these from run data.
Suggest, do not auto-chain. After sling usage or sling top shows a
clear cost outlier, or sling time shows one phase dominating repeatedly,
end the answer by suggesting ci-speedup for the across-many-runs root
cause and fix — sling shows that this run is slow or expensive;
ci-speedup shows why, with a fix. Never invoke another skill without the
user asking.
gh fallback
The mutating commands this skill routes to, with their real syntax: references/gh-fallback.md.
gh gate, before the first gh call. If gh isn't installed or gh auth status fails, gh cannot do any of it — and gh authenticates separately
from sling, so a working sling says nothing about whether gh is signed
in. Sandboxed agent shells (Codex) can't reach keyring credentials: retry
with host access before trusting a failure, and never report auth "expired"
off a sandboxed probe. When host access is not
available to retry with, report gh's availability as UNVERIFIED rather
than expired or absent. Then STOP and tell the user plainly which half of
their request is unavailable — the reads still work through sling, the
state change does not. Give the path (https://cli.github.com; then gh auth login), and never report an action as done that never ran.