Commands
| Command | Does |
|---|---|
shipcheck run [--sha <rev> | --staged] [--no-fetch] | Runs the checks in shipcheck.toml, host checks or workflows, and waits for the verdict. With no --sha, it tests the working copy: tracked edits and untracked files that are not gitignored, which it lists. --staged tests the index. A working copy with no change is HEAD. --sha tests that commit only. Fetches the remote's default branch first unless --no-fetch. A tree that already passed answers at once. A change outside paths runs nothing and prints nothing ran, and that skip is never stored. For each failed job it prints the failed step and its exit code, the failing tests by name, the last 60 lines of the step and the path of the full log. A failed host check gets its tests and the last 60 lines of its output. --json prints one object instead, see JSON |
shipcheck run --job <id> | --workflow <file> | A partial run, for the loop after a failure: runs only those jobs, or every job of those workflows, and the jobs they needs. Both flags repeat. A matrix job's id runs all its legs. --job <file>/<job> picks the job of one workflow. In host mode --job names a [host] check and runs only it, and --workflow is an error. When a host check and an act job share a name, a bare name picks the host check. The result is never stored, so it is never the check: status ignores it, and shipcheck commit refuses after it, even with --allow-failed. A --job that names no job lists each one it takes, with its file and matrix legs, such as test (ci.yml/test, legs test__1, test__2). Exit codes as usual. Fix, run the failed job as often as needed, then one full shipcheck run and shipcheck commit |
shipcheck commit -m <message> | -F <file> [--allow-failed] [--no-verify] | Commits exactly the tree the last shipcheck run of this working copy tested, through git commit, so hooks and signing work as usual. Refuses when a file changed since the run, when the last run was partial, and when the run failed unless --allow-failed. --no-verify goes to git commit. The commit's check is then that run's result. Refuses with exit 2 on the default branch, since PRs carry the checks: git switch -c <branch> first. Plain git commit still works there |
shipcheck doctor | Checks git, act, the Docker engine and its versions, or in host mode the OS sandbox, copy-on-write, the count and size of shipcheck's Docker volumes and images, the remote's default branch and its shipcheck.toml, the network setting, colima when a job uses it, the hooks, whether shipcheck post can post, and where shipcheck's secrets are, reading each so that after an upgrade the keychain asks here. Marks each line ok or fix, a green check or a red cross in a terminal, with the command that fixes it. Exits 0 when a run can work, 1 otherwise |
shipcheck status [<rev> | --staged] [--json] | Prints the stored result for the tree run would test. With no revision it first prints whether the working copy's tree and the index's tree have a result, so you know before a commit whether it reuses one. With no result it exits 2, and with --json it prints {"tree": "<id>", "result": null}. Without --base, a tree that has no result under main's shipcheck.toml shows its last run's, such as one with --base <branch>, and says which base it read |
shipcheck logs [<rev> | --staged] [--base <ref>] | Prints the full logs of that tree, one per check or workflow, then the last partial run's, with control characters escaped. Without --base it finds the last run of the tree as status does |
shipcheck history [--json] [--limit <n>] | Prints the last runs of this repository on this machine, 20 by default: when, commit, mode, result, time, whether a stored pass answered, and what started the run. --json prints the records as one array. See Run records |
shipcheck post [<rev>] [--home-ok] | Posts the stored result of the commit's tree on GitHub as one completed check per job, shipcheck/<job>, and a shipcheck summary. A job id two workflows share gets the workflow in its name, as require the checks says. The job checks are the ones to require. A job main has that the result lacks posts as a failure. Posts only a result read from the daemon's base for this clone or from the default branch the remote reports, so a trial with --base never posts. When the remote does not answer, only the daemon's base posts. Refuses when main has no shipcheck.toml, and when the commit is the tip of main, locally or on origin, since PRs carry the checks. Each job check carries its log tail as logs in shipcheck.toml says, every job by default and always a failed one, and anyone who can read the repository sees it. Each check shows how long its job took. Refuses when SHIPCHECK_HOME, HOME or XDG_STATE_HOME moves the results away from the default, since an agent could have written them there. Pass --home-ok if you set it yourself. The daemon takes --home-ok too |
shipcheck login | Signs this machine in to shipcheck's service with GitHub, so shipcheck post, the pushes' runs and the daemon post the checks. Posting needs the shipcheck App installed on the repository and write, maintain or admin access for you. Prints a code to enter at github.com/login/device, and in a terminal Enter opens that page in the browser. Then it prints signed in as <login>. The machine token stops working after 90 days without a post, then run it again. On macOS it goes in the login keychain, see where shipcheck keeps its keys |
shipcheck logout [--all] | Revokes this machine's token and deletes it. With SHIPCHECK_TOKEN set, revokes that token instead and keeps the stored login token. --all revokes every token of your GitHub user, on every machine, and prints how many. When this machine's token is no longer valid, --all revokes nothing and fails, so sign in again first |
shipcheck token [--name <machine>] | Makes a machine token for another machine to use as SHIPCHECK_TOKEN. Prints a code to enter at github.com/login/device on stderr, as login does, then only the token on stdout. Leaves this machine's sign-in alone. --name labels it, token by default. Each user can hold 50 live tokens |
shipcheck init [--yes] | For the repository owner: turns each workflow job it can into a host check and each job act can run as GitHub does into a [jobs] act entry, tries each host check in the sandbox, writes shipcheck.toml with the ones that pass, writes the agent instructions into AGENTS.md and the other files agents read, and runs install. On a terminal its steps are tabs, Checks, Install, Workflows, GitHub and Review, which also cover where each job runs, the App, hosted Actions only on main, and a ruleset that protects the default branch. It changes nothing until you apply on the Review tab. --yes applies the recommended choices without asking, but opens no browser and creates no ruleset. Without a terminal and without --yes it only previews: it prints the plan --yes would apply and writes nothing, so an agent or a script passes --yes. It leaves an existing shipcheck.toml as it is. When the remote does not say its default branch, it refuses and says to run it again online. See the quickstart |
shipcheck install | For each clone: installs the pre-push and post-commit hooks, removes the agent hooks an older version wrote, and starts the daemon service for this repository, with the default branch the remote reports as its base. When the remote does not answer, it adds no repository to the daemon and says to run it again online. Running it again changes nothing, and an error with the service is one line that never fails the install. In a terminal, it signs the machine in when it cannot post yet. Without one it prints how to. See Agents |
shipcheck daemon [--home-ok | --uninstall] | init and install start it as a background service, a launchd agent on macOS or a systemd user unit on Linux, and add the repository to daemon.toml in SHIPCHECK_HOME. It checks the open PRs into the configured branch within seconds of each push to a PR, from the App's webhook through shipcheck's service, and every interval_seconds (10 by default) while it cannot hear those events or a head is still waiting. A head with no completed shipcheck check gets its stored result at once, or, with no result, a run that posts in progress and then the result, as a push does. It runs only PRs you, or a login daemon_authors lists, both opened and pushed the head of, never a head that contains one it refused, and never a fork's. It posts with the shipcheck login token, and runs no PR until the machine is signed in. --uninstall stops and removes the service, but only from the default SHIPCHECK_HOME, since the service belongs to it |
shipcheck trust [--remove] | Lets this repository's host checks with sandbox = false on main run outside the sandbox on this machine, with your full access. Without it they run in the sandbox. --remove takes it back. It records the origin URL in trusted in SHIPCHECK_HOME (outside the sandbox) |
shipcheck clean [--all] | Removes what shipcheck keeps for this repository: its Docker cache volumes, warm checkouts and run slots, host mode's tool caches in hostcache/ in SHIPCHECK_HOME, and the colima VMs this home recorded and a dead run left. --all does it for every repository, also deletes the colima base VM, removes stored results, logs and run records, and removes the daemon service when SHIPCHECK_HOME is the default. It never removes a Docker image. A volume that a container uses, running or stopped, stays, and so does a tool cache a run holds. Everything rebuilds on the next run, which is slower once. Refuses while a run is going |
The git hooks call shipcheck on-push and shipcheck on-commit. Those start a background run when on asks for one, never fail, and never make git wait. On a machine that can post, a push's run posts the checks. They read shipcheck.toml from the base install stored for the clone, else origin/main, and never ask the remote, so git does not wait on the network. With no stored base on a repository whose default branch is not main, they print one line to stderr that says to run shipcheck install. You do not call them yourself.
--base <ref> reads shipcheck.toml and the workflows from another ref than the default branch, and turns off the fetch. A result from another ref is a trial, which post refuses. It is for trying shipcheck on a repository before the file is on main.
JSON
--json prints one object on stdout and the progress on stderr. Exit codes stay the same.
{
"tree": "4b825dc6...", "sha": "9f2c1e0a...", "partial": false, "passed": false, "could_not_run": false, "skipped": null, "ci_changes": [],
"workflows": [{
"file": "ci.yml", "passed": false, "exit": 1, "attempts": 1, "log": ".../logs/<key>/ci.yml.log",
"jobs": [{
"id": "test", "status": "failure", "attempts": 1, "shards": null,
"failed_step": "Run pytest", "exit": 1, "note": null,
"failing_tests": ["tests/test_math.py::test_add"], "failing_tests_total": 1,
"tail": ["...", "FAILED tests/test_math.py::test_add - assert 3 == 4"],
"log": ".../logs/<key>/ci.yml/test.log", "attempt_logs": []
}]
}]
}
Test names come from pytest, cargo test, nextest, go test, jest, vitest, rspec and phpunit output, at most 30. sha is working copy or index when the run tested one of those. partial is true for a --job or --workflow run. In host mode, one workflow named host holds a job per check, with no failed_step or exit, and tail is the end of the check's output. failing_tests_total counts them all. tail holds at most 60 lines, with control characters escaped. note explains a failure that is not the code's, such as a stale checkout or a job with no image on this host.
Run records
Each run adds one line to records/<owner>/<repo>.jsonl in SHIPCHECK_HOME: when it ran, the commit and tree, the branch, what started it, the machine name and OS, the shipcheck and tool versions, whether a stored pass answered, and for each job its status, attempts, times and failing test names. The daemon also adds the PR number and author, whether it posted the checks and the comment, when a PR it checked gets a new head, and when one is merged and by whom. A record holds no code, file contents, log output, environment values or secrets.
shipcheck history reads them from this file.
Records are not deleted with old results and logs. A file past 50 MB moves to <repo>.<n>.jsonl. To delete them, remove the records folder, or run shipcheck clean --all, which also removes results, logs and warm checkouts.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Every check passed, or nothing under paths changed |
| 1 | A check failed, or doctor found something to fix. The output names the failed step and tests, shipcheck logs prints every log |
| 2 | shipcheck could not run, for example shipcheck.toml is missing on main or invalid, the OS sandbox is missing in host mode, or act is not installed in act mode. The message says what to fix. For status, the tree has no result yet |
Environment
| Variable | Default | Meaning |
|---|---|---|
SHIPCHECK_HOME | $XDG_STATE_HOME/shipcheck, else ~/.local/state/shipcheck | Where results, logs and warm checkouts live. Only ~/.local/state/shipcheck counts as the default home: when SHIPCHECK_HOME or XDG_STATE_HOME moves it, post and the daemon refuse without --home-ok, and init and install leave the daemon service out. A home left at ~/Library/Application Support/shipcheck by an older shipcheck moves here on first use. The default is inside a dot folder, so a test that refuses paths with a hidden folder in them fails there: set SHIPCHECK_HOME elsewhere, and pass --home-ok to post. Background runs log to logs/background.log in it. At the start of each run, results untouched for 14 days, logs untouched for 90 days and warm checkouts no run used for 7 days are deleted. At its end, the Docker volumes of this home that no run used for 14 days are removed, unless a container uses them, and so are the runner images an older shipcheck pinned. shipcheck never removes a volume it did not make, or an image a workflow names |
SHIPCHECK_TIMINGS | unset | Set, run prints how long each phase of each job took to stderr, such as the clone, act's start and the cleanup |
SHIPCHECK_MAX_RUNS | a quarter of the CPUs, at least 1 | How many runs the hooks and the daemon start at once on this machine. Others wait for a slot. shipcheck run never waits |
SHIPCHECK_API | https://api.shipcheck.run | The service login, logout, token and post talk to |
SHIPCHECK_NO_DAEMON | unset | 1 makes init and install leave the daemon service out on this machine |
SHIPCHECK_SKIP | unset | 1 on a commit or push starts no background run, see skips |
SHIPCHECK_TOKEN | the token from shipcheck login | A machine token for post, for an unattended machine such as a VM or an agent box. Make one with shipcheck token. When set, post uses it instead of the login token, and logout revokes it. The daemon service gets only PATH, so it never sees it (the daemon) |