docs / automation
Automation & CI
The reading for scripts, CI jobs, and agents: how to branch on exit codes, what is inside a --json report, the action vocabulary, the full pattern for wiring skillmod into a pipeline, and the conventions for non-interactive environments. Hands-on sequences by task live in the scenario guide, and the per-command flags in the command reference.
Exit codes
Read the exit code when a script, a CI job, or an agent needs to decide something from a skillmod run instead of a person reading its output:
| Exit | Meaning | When it appears |
|---|---|---|
0 | All checked entries are consistent | An inspection or operation completed normally |
1 | Operational or input error | Any command |
2 | Drift detected | verify finds contents that do not match SKILL.lock |
3 | Partial completion: independent work finished, and one or more targets were safely preserved | get, sync, update, and remove keep a target instead of overwriting it |
get, sync, update, and remove return 3 when they keep a target instead of overwriting it. That is not total failure and not total success: read the report to see what was preserved and why.
Drift gets its own 2 because it is a different conclusion from “something went wrong.” The job of verify is checking consistency: contents that do not match the lock record are a definite state judgment, worth separating from argument errors and environment problems so a script can handle each on its own.
2 means the checked-out skill environment does not match the declaration, and 3 means targets were safely preserved — either one should stop the pipeline, with the report left for troubleshooting.
--json
Every command supports --json. The command prints its human summary as usual; adding --json also emits a structured report on stdout.
A report separates the command that ran from the outcome of each declaration and of each installation directory:
| Field | Meaning | action values |
|---|---|---|
action | The command that produced the report | get, init, list, prune, remove, share, sync, update, upgrade, verify, why |
entries[].action | The aggregate outcome of one declaration | conflict, drift, install, installed, keep, local, local-drift, matched, missing, partial, prune, remove, skip, stale, unlocked, unresolved, unverifiable, update |
entries[].targetResults[].action | The outcome of one installation directory | drift, install, installed, keep, missing, remove, skip, unlocked, unverifiable |
Field names and action identifiers are never translated. Branch on the command and action identifiers, not on natural-language notes: a note is translated prose for people, and it changes with the output language.
Inspection reports (list, why, and verify) also carry requestedVersion for remote entries: it is the exact SKILL.mod value, while version is the installed version from SKILL.lock. An empty requestedVersion means the declaration tracks latest, which the lock pins.
upgrade uses the same shape with action: "upgrade" and one entry named skillmod. The entry action is keep when the running version is the requested one and update when a newer release exists, with entries[0].version naming that release; the executable's own targetResults[0].action says what happened to the file: installed means it was replaced, install means a dry run verified it, and keep means it was left alone. The report is written even when the upgrade fails, so --json always decodes one document.
Action vocabulary
The command level is the eleven command names themselves: whichever command produced the report is the top-level action. The full vocabulary for the entry and directory levels follows.
entries[].action (the aggregate outcome of one declaration)
| action | Meaning |
|---|---|
conflict | The target directory holds different content, settled by the conflict policy |
drift | Contents do not match the lock record |
install | A planned installation that a dry run verified, not yet written |
installed | Written, and matching the lock record |
keep | Already as expected, left alone |
local | A local declaration, with no remote source |
local-drift | The local entry was edited and no longer matches the recorded baseline |
matched | init matched the source of an existing lock record or verified snapshot |
missing | The installation directory does not exist |
partial | Partial completion: some work finished, and targets were preserved |
prune | prune removed a stale installation or lock record |
remove | The declaration (or a share link) was removed |
skip | The target was skipped by the conflict policy |
stale | The declaration is gone from SKILL.mod while the installation remains; clean it with prune |
unlocked | The declaration has no matching record in SKILL.lock |
unresolved | The source cannot be resolved to a Git repository and is kept as a local entry |
unverifiable | The directory cannot be read or its contents cannot be verified |
update | Updated; in an upgrade report, a newer release exists |
entries[].targetResults[].action (the outcome of one installation directory)
| action | Meaning |
|---|---|
drift | Directory contents do not match the lock record |
install | A planned installation that a dry run verified |
installed | Written, and matching the lock record |
keep | The directory is already as expected and was left unchanged |
missing | The directory does not exist, or a share link is absent |
remove | The directory (or share link) was removed |
skip | Skipped by the conflict policy |
unlocked | No lock record to check the directory against |
unverifiable | The directory cannot be read or its contents cannot be verified |
The stability conventions of the report shape:
- Branch only on the command identifier and the action values at each level: they are stable and never translated; a
noteis translated prose and is not something to branch on. entries[].actionis an aggregate that folds the entry's target results into the most actionable status; readtargetResultswhen one directory matters more than the declaration as a whole.- Per-directory facts appear only in
targetResults: a conflicting or unreadable directory stays visible there while the rest of the command succeeds. requestedVersionappears only on remote entries of inspection reports, and an empty value means the declaration tracks latest.- The
upgradereport is written even when the upgrade fails, so--jsonalways decodes one document.
Wiring into CI
The full pattern is four steps: checkout, install skillmod, sync, verify. verify is the build gate — a nonzero exit fails the build.
Wire the exit code of verify into the pipeline explicitly:
syncis idempotent: it installs or aligns declared content againstSKILL.lockand does not silently overwrite local modifications;verifychecks the result after alignment.- Add
--jsonfor a machine-readable conclusion, branch on the command and action identifiers, and keep the report as a build artifact for troubleshooting. - CI has no TTY: give the selection up front for any command that could open one, as described in the next section.
- To watch for a new skillmod release in the pipeline without downloading it:
skillmod upgrade --check --json.
sync in a clean environment usually needs access to the repositories.
Non-interactive conventions
CI and scripts have no TTY, so any command that could open a selection needs that selection given up front. --all and --yes divide the work: --all states the selection itself — every skill the repository publishes for get, every declared entry for remove, every installed skill for share; --yes answers the confirmations that follow a selection and never decides what the selection is.
A non-interactive run of a multi-skill repository has to say --all to get past the selection step. On a terminal, --yes still lets the user pick skills; it skips only the later confirmations. The same rule applies to share and remove.
The use of --dry-run is to review the report before the write: init, sync --relink, remove, prune, share, and upgrade all support it. upgrade --dry-run downloads and verifies without replacing the executable, and --check stops before downloading. A dry run writes no state and creates no links, so it suits confirming before acting.
Language and environment
Command help, summaries, prompts, and errors follow SKILLMOD_LANG; when it is unset, the system locale is read from LC_ALL, LC_MESSAGES, then LANG, falling back to English when none is available or supported.
JSON field names and action identifiers are unaffected: a script can branch on the identifiers whether or not SKILLMOD_LANG is set. Setting it keeps the human-readable output in one language — worth fixing in CI logs, so prompts do not change shape with the runner.