| name | ci-setup |
| description | Check CI prerequisites and configure the self-hosted runner SSH config. Use when first installing the plugin, after adding runners, or when CI commands fail with auth or connectivity errors. |
What It Does
Verifies the CI prerequisites (gh, jq, ssh) and GitHub CLI authentication,
then optionally walks a wizard to configure the plugin's runner SSH config —
previewing the config and confirming before any write.
When to Use
- First installing the plugin, after adding runners, or when CI commands fail
with auth or connectivity errors.
Usage
This skill is wizard-driven and takes no arguments; the argument text after the
skill name (if any) is available as context.
Config location. This skill operates on the plugin's runner SSH config
file, yellow-ci.local.md. When an invoking command supplies a host-resolved
path (on Claude Code, the repo-local plugin config), use that path. Otherwise
— for example, on direct invocation with no invoking command — fall back to
the host-neutral default:
${XDG_CONFIG_HOME:-$HOME/.config}/yellow-ci/yellow-ci.local.md. Never
hard-code a host-specific config directory into this body.
Runner scope. yellow-ci targets Linux self-hosted runners. Windows/macOS
runners are not probed by the health/cleanup workflows; note this if a
non-Linux runner is configured.
Step 1: Check Prerequisites
Run all prerequisite checks in a single Bash call:
printf '=== Prerequisites ===\n'
command -v gh >/dev/null 2>&1 && printf 'gh: ok\n' || printf 'gh: NOT FOUND\n'
command -v jq >/dev/null 2>&1 && printf 'jq: ok\n' || printf 'jq: NOT FOUND\n'
command -v ssh >/dev/null 2>&1 && printf 'ssh: ok\n' || printf 'ssh: NOT FOUND (needed for runner health/cleanup)\n'
Collect all missing-tool failures before stopping — report them together.
Stop conditions (after reporting all failures):
ssh missing is a warning only — it affects the runner health, cleanup, and
self-hosted-optimization workflows. Diagnosis and linting are unaffected.
Step 2: Check GitHub CLI Auth
gh auth status 2>&1
- Non-zero exit: if the output says "not logged in", report "GitHub CLI is
not authenticated. Run:
gh auth login" and stop; if it looks like a network
error, report "Could not reach GitHub API — check connectivity." and stop.
- Zero exit: record PASS; parse the logged-in account and the
Active token scopes: line. Warn (do not stop) if repo or workflow are
absent, pointing at gh auth login --scopes repo,workflow,read:org.
Step 3: Check Existing Config
Check whether the runner SSH config file already exists and its YAML front
matter (after the opening --- delimiter) contains a schema: 1 line. If it
does, read it, count runners
(awk '/^[[:space:]]*- name:/{c++} END{print c+0}' — always emits one integer),
list the runner names, and ask via AskUserQuestion: "Runner config already
exists (N runner(s): …). Reconfigure?"
- No → show the current config summary and stop.
- Yes → continue to Step 4.
If absent, continue to Step 4.
Step 4: SSH Config Wizard (Optional)
Ask via AskUserQuestion: "Do you use self-hosted GitHub Actions runners?
(Required for runner health and cleanup)".
- No → prepare a minimal config (no runners block); skip to Step 5.
- Yes → collect runner details below, looping until the user says done.
For each runner, collect and validate (reject and re-prompt on failure —
never warn-and-continue):
- Runner name — must match
^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$ (length 2-64,
DNS-safe).
- SSH host — accept only a private IPv4 (
10.x, 127.x, 172.16-31.x,
192.168.x) or an internal FQDN ending in .internal, .local, .lan,
.corp, .home, .intra, or .private. Public IPs and public-TLD hostnames
are rejected — private network only.
- SSH user — must match
^[a-z_][a-z0-9_-]{0,31}$.
- SSH key path (optional) — if provided, must start with
~/ or /,
contain only safe characters, and contain no .. or shell metacharacters.
Reject the ~user/... form: it would pass a looser "starts with ~" check,
but ci-runner-health's expansion only resolves ~/, so such a key would
reach ssh as a literal tilde path and silently fail the probe. Accepting
only the forms that are actually expanded keeps setup and health-check in
agreement on which key paths are usable.
Security — never place a raw collected value inside a shell heredoc body.
A quoted delimiter (<<'EOF') only blocks $/backtick expansion inside the
body; it does not stop the body from containing a line that matches the
delimiter itself. A value with an embedded newline followed by that line
closes the heredoc early, and everything after it runs as shell commands —
before any pattern check above ever sees the value. Do not "fix" this with a
longer or more unusual delimiter either: this skill body is committed to the
repo and world-readable, so a deliberately hostile answer can just reuse
whatever delimiter is documented here — a unique delimiter only rules out
accidental collision, not a chosen attack.
Instead, treat every raw answer as opaque until it has been checked: write it
to a fresh temp file with the Write tool (its content is a structured
parameter, never parsed as shell) before it touches any Bash command, then run
the pattern check above by reading that file's content (for example, grep -Eq '<pattern>' "$tmpfile") — never by interpolating the raw text inline in a
command string or heredoc body. Reject and re-prompt on any check failure, and
delete the temp file once the field is accepted or rejected. Only a value that
has passed its check — which already excludes newlines, quotes, $, ;, and
other shell metacharacters — may be reused afterward.
Step 5: Preview and Confirm, Then Write Config
Preview first (R32). Render the exact config content (schema header, each
- name: runner block with host/user/optional ssh_key, and the defaults
block) and show it to the user. Then ask via AskUserQuestion: "Write this
runner config? [Write / Edit / Cancel]". Only write after explicit
confirmation; on a host without AskUserQuestion, obtain an equivalent explicit
user confirmation first — never write config without one. Write the confirmed
content with the Write tool, not a shell heredoc or redirection — the
rendered content still carries the collected host/user/name values, so
Step 4's no-heredoc rule applies here too.
Config shape (with runners):
---
schema: 1
runners:
- name: [name]
host: [host]
user: [user]
ssh_key: ~/.ssh/runner-key
defaults:
ssh_timeout: 3
max_parallel_ssh: 5
cache_dirs:
- /home/runner/.cache
log_retention_days: 14
docker_prune_age: 168h
---
Configured on [ISO-8601-UTC timestamp]. Edit this file directly to add runners
or change defaults, or re-run setup to reconfigure interactively.
With no runners (user declined), write the same shape with a commented-out
# runners: [] placeholder and the defaults block. Create the parent config
directory first if it does not exist. Obtain the timestamp with
date -u +%Y-%m-%dT%H:%M:%SZ.
Step 6: Validate the Written Config
Confirm the written file's YAML front matter (after the opening ---
delimiter) contains schema: 1. Then re-read it and verify: the runner count
matches the number collected in Step 4, and each runner name entered appears
verbatim. On any mismatch, report it and stop.
Step 7: Report
Report prerequisites, GitHub CLI account/scopes, and the runner config summary
(where it was written, runner count, names). If the config was written to a
repo-local path (an invoking command's host-resolved path inside the
repository), advise: the config contains runner hostnames — if this is a
shared repository, add the config to .gitignore to avoid committing host
details. The host-neutral fallback location lives outside the repository, so
this advice does not apply when the config was written there.
Error Handling
- Missing
gh/jq → collect and stop with install links; missing ssh →
warn and continue.
gh not authenticated → "Run: gh auth login"; network error → "Could not
reach GitHub API".
- Invalid runner name / SSH host / SSH user / SSH key path → report the specific
rule and re-prompt.
- Config directory not creatable, or write/validation failure → report a
permissions hint and stop.
Success Criteria
- Prerequisites and auth are reported accurately.
- The runner config is previewed and confirmed before any write, validated
after writing, and the written runner names/count match what was collected.