# Quickstart

https://shipcheck.run/docs/quickstart/

> Set up a repository once, then each clone once, then work as usual.

## Setup

1. Install the binary: `brew install gabrielkoerich/tap/shipcheck`, or `curl -fsSL https://shipcheck.run/install.sh | sh` ([install](https://shipcheck.run/docs/install.md)).
2. Run `shipcheck init` in a clone of the repository, on `main`.
3. Commit the `shipcheck.toml` it writes and merge it to `main`.
4. Install the [shipcheck App](https://github.com/apps/shipcheck-run) on the repository. `init` prints this link.

Each other clone then needs `shipcheck install` once. The sections below say what each step does.

## For the repository owner, once

In a clone of the repository, on `main`:

```sh
shipcheck init
```

It reads your workflows and project files and changes nothing until you apply. Its steps are tabs: Checks, Install, Workflows, GitHub and Review. Move between them with the arrow keys, Tab or 1 to 5, and go back to any of them. Each starts on the recommended choice. On Checks, Space or Enter moves a job to another place it can run: a host check, act on Docker, act on a colima VM where colima could run it, or GitHub. A job becomes a host check only when it ran as one in init's trial. Apply on the Review tab. Quitting, or Ctrl-C at any point before you apply, leaves the repository and the machine as they were.

An agent or a script runs `shipcheck init --yes`, which asks nothing and applies the recommended choices, except the browser and the ruleset. Without a terminal and without `--yes`, init only previews: it tries each check with a temporary `SHIPCHECK_HOME`, prints the plan and the review `--yes` would apply, and writes nothing.

### Checks

`init` turns each job of a workflow that GitHub runs for feature branches into a check named like the job.

- The job's `run` steps are joined by `&&`.
- A job with `needs` runs the jobs it needs first.
- Package installs into the checkout, such as `npm ci`, `bun install --frozen-lockfile` or `uv sync`, stay in the check.
- The workflow's and the job's `env` are exported first, and a step's `env` only in that step.
- Commands that set up the machine, such as `apt`, `sudo`, `curl`, `brew`, `rustup`, `pip install`, `go install`, `git config` or `gh`, are left out. A step that needs root besides other commands, such as one that makes and mounts a volume, is left out whole, and so is a `TMPDIR` or `HOME` value, since the sandbox sets both.
- Steps with a GitHub expression, also in their `env`, `if:` or `continue-on-error` are left out, and so are multi-line steps with control flow.
- A setup action that installs into the checkout keeps its install: `pnpm/setup` (unless `install: false`) and `pnpm/action-setup` with `run_install: true` add `pnpm install`, `ruby/setup-ruby` with `bundler-cache: true` adds `bundle install`. `setup-node` and `setup-uv` caches install nothing. `gitleaks/gitleaks-action` becomes a `gitleaks dir --redact` scan of the commit's tracked files, which leaves out the build output a warm clone holds. A history scan stays on GitHub.
- Deploy, publish and release jobs are left out, and so are jobs that only install, move files or run git, jobs that download artifacts from a job with no check, and jobs that need a Rust target (`rustup target add`, `targets`). A pinned Rust toolchain, such as `toolchain: "1.94.1"` for `dtolnay/rust-toolchain` or `actions-rust-lang/setup-rust-toolchain`, is pinned with `RUSTUP_TOOLCHAIN` when rustup has it here. Otherwise the check runs on the default toolchain, and init lists it under "run on this machine's version, not the workflow's" until you run `rustup toolchain install 1.94.1`. A `rust-toolchain.toml` needs nothing, since rustup reads it.
- A job no host check runs as GitHub does, such as one with a dropped step, a `container:` or a Linux matrix, runs with act as a `[jobs]` entry when Docker is running, and init adds `network = "internet"`. A job stays on GitHub when act cannot run it as GitHub does either: Windows jobs, macOS jobs off a Mac, matrices with macOS or Windows entries, and jobs that read the PR anywhere, report to GitHub, or run Docker. init writes `engine = "colima"` only when you move a job to a colima VM on the Checks tab. A matrix of one combination, such as `python-version: [3.13]`, is a plain job. [Which jobs run where](https://shipcheck.run/docs/how-it-works/#which-jobs-run-where) has the table.

Only when no job gives a check, a `package.json` `test` or `lint` script, a `Cargo.toml`, a `pyproject.toml` or a `go.mod` at the root gives the checks they imply. A repository with no `.github/workflows` gets its checks this way, in host mode, the only mode it can use.

It shows each check with the file it came from, and what it left out and why. The checks run natively in a sandbox, and before the review init runs each one there once, as `shipcheck run` would. A check that fails runs again without forced color, then with color off. A job whose check still fails stays on GitHub, and init prints the end of its log and the fix. init does not try the act jobs, so a failing one shows on its first run. Edit `shipcheck.toml` afterwards to drop one:

```toml
runner = "host"

[host]
versions = ["cargo --version", "rustc --version"]

[host.checks]
test = "cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test"
service = "cd service && bun run test"
```

shipcheck has two [modes](https://shipcheck.run/docs/config/#modes). `init` writes host mode when it finds checks, with act entries for the jobs a host check cannot run as GitHub does. It writes act mode for the whole repository when a job runs a Linux matrix, every job runs on Linux and Docker is running, since host mode tests only this machine's toolchain:

- Host mode runs your commands natively in an OS sandbox, each in a warm copy-on-write clone. It is fast, uses the tools on your machine and needs no Docker.
- act mode, `runner = "act"`, runs your GitHub Actions workflows in containers. It is the closest to GitHub's runners and slower to start. It needs act and a running Docker engine, and so does each `[jobs]` act entry in host mode ([install](https://shipcheck.run/docs/install.md)). A `shipcheck.toml` with no `runner` line runs act mode.

### GitHub: the App

It shows the install link of the [shipcheck App](https://github.com/apps/shipcheck-run), which posts the checks, and opens it in the browser when you apply. Install it on the repository.

Pick your user or organization and the repositories there ([install the App](https://shipcheck.run/docs/install/#install-the-app)). At the end, `init` signs this machine in if it cannot post yet ([sign in each machine](https://shipcheck.run/docs/install/#sign-in-each-machine)).

### Workflows: hosted Actions only on `main`

For each workflow that GitHub runs for feature branches, it shows one line, such as `ci.yml: on PRs shipcheck runs shipcheck/clippy, shipcheck/fmt, shipcheck/test, GitHub keeps running ci.yml on pushes to main, and on fork PRs`, and a diff. The workflow keeps its triggers. Each job a shipcheck check covers gets an `if:` that runs it on GitHub only on your default branch, on the other branches the workflow lists for pushes, on PRs into other branches, for Dependabot, and for a fork's PR, which shipcheck never runs. A job no check covers stays on GitHub with no `if:` added ([which jobs run where](https://shipcheck.run/docs/how-it-works/#which-jobs-run-where)).

- Comments and other triggers stay.
- The recommended choice is yes. init compares the commands, not the names, so a check called `fmt` covers a job called `test` that runs `cargo fmt --check`. The line names the jobs GitHub keeps running on PRs.

### GitHub: branch protection

It shows one check name per job, such as `shipcheck/test`, and GitHub's own check names of the jobs that stay on GitHub. The recommended choice protects the default branch with a ruleset, created with your own `gh` login: no direct pushes, only PRs, and a PR merges once its branch is up to date and each of those checks passes, from the App or from GitHub Actions.

For a job on GitHub whose check names init cannot tell, such as a job whose name or matrix comes from an expression, the ruleset keeps the checks the branch requires now. When `gh` cannot read the branch's rules, init makes no ruleset and leaves them as they are. Install the App before the first PR, since a required check that nobody posts blocks it. When your `gh` login has no admin rights on the repository, init says so and gives the link to set the ruleset up by hand, and the rest still applies. Update the ruleset when a job is added, renamed or removed. [Require the checks](https://shipcheck.run/docs/branch-protection.md) has the manual steps.

### Install

It writes the agent instructions ([agents](https://shipcheck.run/docs/agents.md)). Then, as `shipcheck install` does below, it removes the agent hooks an older version wrote, installs the git hooks for this clone, signs the machine in if it cannot post, and starts the daemon.

### Other cases

- When `shipcheck.toml` already exists, `init` leaves it as it is.
- Without a terminal and without `--yes`, as for an agent or a script that forgot the flag, it prints the plan and writes nothing: no `shipcheck.toml`, no `AGENTS.md`, no hooks, no daemon and no sign-in. Run `shipcheck init --yes` to apply it.
- When every job stays on GitHub, or no check passes when init tries it, it writes no `shipcheck.toml`, says why, and stops: no App, workflow or ruleset step, no agent instructions, hooks or daemon.
- When it finds no check in the workflows or the project files, it writes `runner = "act"` with every workflow in `workflows`, run in containers, and `shard_tests = true`. Remove the workflows that must not run on a laptop, such as deploys. [Modes](https://shipcheck.run/docs/config/#modes) compares the two. With no workflows and none of those project files, it stops with an error.

Add `on = ["push", "commit"]` to start runs on each commit too.

Commit it and merge it to `main`. shipcheck reads this file and the workflows only from `main`, so a PR cannot change what it must pass. Until the file is on `main` there is nothing to run: `shipcheck run` exits 2, and the hooks and the daemon start nothing. [Every setting](https://shipcheck.run/docs/config.md).

Your workflows keep running on GitHub on pushes to `main`, as a backstop after each merge.

### Add the badge

To show that the repository's checks come from shipcheck, add this line to its README:

```markdown
[![checks: shipcheck](https://img.shields.io/badge/checks-shipcheck-1f9d55)](https://shipcheck.run)
```

[![checks: shipcheck](https://img.shields.io/badge/checks-shipcheck-1f9d55)](https://shipcheck.run)

## For each teammate, once per clone

```sh
shipcheck install
```

It installs two hooks, pre-push and post-commit. The post-commit hook starts a run only when `on` has `commit`. It also starts the daemon as a background service, a launchd agent on macOS or a systemd user unit on Linux, and adds this repository to it. Set `SHIPCHECK_NO_DAEMON=1` to skip that on one machine. Git never installs hooks on clone, so each clone needs this one step. To make it automatic, run it from the repository's usual setup: the `prepare` script in `package.json`, a `just setup` recipe, or a `mise` task.

If the repository already uses husky, lefthook or another hook manager, through `core.hooksPath` or a `pre-push` hook of its own, `shipcheck install` leaves its hooks alone and stops with an error that gives the line to add. It then does not sign the machine in or start the daemon, so run it again after you add the line. A `post-commit` hook of its own only gets a note with the line.

When the machine cannot post yet, `install` signs it in: in a terminal it prints a code to enter at github.com/login/device. Without a terminal it prints one line and goes on.

## For an agent machine

1. [Install the binary](https://shipcheck.run/docs/install.md).
2. Run `shipcheck install` in each clone. In a terminal, it signs the machine in if it cannot post yet.

A machine with nobody at its terminal, such as a VM, gets a token instead: run `shipcheck token --name <machine>` where you can open a browser and set the output as `SHIPCHECK_TOKEN` there ([unattended machines](https://shipcheck.run/docs/install/#unattended-machines)).

## Every day

Commit and push as usual. Nothing waits on shipcheck:

- Each commit or push starts a run in the background, as `on` says.
- A newer commit on the same branch stops the older run.
- A tree that was already tested needs no run at all.
- The checks go on PRs, so `shipcheck commit` and `shipcheck post` refuse on the default branch. `shipcheck run` works on any branch.
- With the App installed and the machine signed in, a push is enough. The run it starts posts one check per job on the PR, such as `shipcheck/test`, in progress at once and then green or red, and a `shipcheck` summary of the run.
- When a PR changes on GitHub, such as a rebase with the Update branch button, the daemon reruns its checks ([how it works](https://shipcheck.run/docs/how-it-works/#when-a-pr-changes-on-github)).

To run the checks in the foreground and wait for the verdict, for example as an agent's test step before it pushes:

```sh
shipcheck run
```

When a run fails, give the agent the logs:

```sh
shipcheck logs
```
