What a green check proves
A green shipcheck/<job> check on a PR means shipcheck ran that job on the PR's tree, with the config from main, and the job passed. The rules below hold for every check.
What it guarantees
- The result comes only from a run shipcheck started. shipcheck reads the exit codes of the processes it started. Nothing accepts a result, a log or a verdict from an agent or a script, so an agent that says the tests passed changes nothing.
shipcheck.tomland the workflows come frommain. shipcheck never reads them from the branch or the working copy. A PR that edits a workflow toexit 0, or emptiesworkflows, is still checked withmain's version.- The job list comes from
main. A job thatmainhas and the result lacks fails, so a PR cannot drop one. - Each job runs in a new copy-on-write clone of the tested tree, never in the agent's worktree, and never in a checkout another job used. Before the job starts, shipcheck hashes every file of the clone again, and the job fails unless they make exactly the tested tree.
- A job passes only when its process exits 0. In act mode every job that started must also report
success. A timeout, a missing job or an error is a failure. - A result belongs to a git tree, never to a commit. A commit gets a stored pass only when its tree is the tree that ran, so a commit with one more file runs again. The key also holds the config on
main, the workflows as they run, the tool versions and the images, so a change to any of them runs again (when a stored result counts). - Only a pass is reused. A failure runs again next time.
- A
pathsskip is worked out again on each run and never stored. Any doubt about which files changed means run. - With branches required to be up to date, the tested tree is the tree a squash merge puts on
main(require the checks).
How it knows it tested the right code
A check names the code it tested by hashes, and shipcheck checks each hash itself instead of trusting a file or a caller.
- The commit names its code. Git names every commit and tree by a hash of its content, so a tree id is a hash of every file in the commit. A result belongs to a tree. After a rebase or a push the PR has a new head commit. The daemon takes that commit's SHA from GitHub, fetches it and reads its tree. A new tree runs again. A tree that was already tested, such as a rebase that changed nothing, reuses its result.
- Each job's files are that tree. After the clone and before the job starts, shipcheck hashes every tracked file on disk again, into an index with no cached file times. The tree written from that index must be the tested tree. An edited, deleted or relinked file, or a changed mode, fails the job. Build output and untracked files are not part of a tree.
- The build starts from those files. Every tracked file gets the run's start time, so cargo, make and other tools that compare file times rebuild the code from this tree. They never trust output another tree left.
- What the check shows is true before it is posted. Before any pass is posted, stored or fresh, shipcheck checks it again. Its tree must be the commit's tree, its base must be on the current
main, and each log must hash to the digest in the result. A stored result that fails this does not count, and the commit runs again. A fresh one is posted as a failure.
Check it yourself
The shipcheck check on a PR lists the Tree, the Base, the Key and a Log SHA-256 per workflow. From a clone of the repository:
| Field | Command | Holds when |
|---|---|---|
| Tree | git rev-parse <sha>^{tree}, with <sha> the PR's head | it prints the Tree |
| Base | git merge-base --is-ancestor <base> origin/<branch>, with <branch> the default branch git ls-remote --symref origin HEAD reports | it exits 0 |
| Log SHA-256 | shasum -a 256 ~/.local/state/shipcheck/logs/<key>/<workflow>.log on the machine that ran it, where <workflow> is the workflow file or host for host checks | the first 12 hex digits are the Log SHA-256 |
shipcheck logs <sha> prints the same logs. The Key is a hash of the tree, the config on main, the workflows as they ran, the tool versions, the shipcheck version and the images. Only a machine with the same tools gets the same Key, so it shows when two results came from the same inputs.
What it does not prove
- That the tests are good. Tests are part of the PR, so an agent can delete, skip or weaken one and the run passes. The check names changes to CI files and to what a command runs, such as
package.jsonscripts, for the reviewer. Review test changes. - That the code passes on GitHub's runners. Host mode tests this machine's OS and toolchain, and act's images only resemble GitHub's. Your workflows still run on GitHub after each merge to
main. - That the suite passed in order, with
shard_testson. - Which binaries ran. Build output is reused between runs to keep them fast. shipcheck makes the build tools see every file of the tree as new, so they rebuild the code, and it does not yet record what they built.
- That no one forged a result. shipcheck runs as your user and keeps its results in a folder your user can write. It stops an agent that claims a pass. It does not stop one that sets out to write a fake result there. Keep that folder outside your agent's sandbox (agents).
Limits has the full list.