Skip to content
Shipcheck

Reference

Checks

Shipcheck runs ten small checks, in this order. Each one passes, warns, fails or is skipped, and failures are what block shipping.

Every check can be turned off or have its severity changed in .shipcheck.yml. By default, only the problems marked ✗ below block shipping.

Git

Repository present, working tree clean, on the branch you ship from.

Git repository

id
git.repo
config key
git_repo

What it checks. Checks that the project is inside a Git repository. Works from any subdirectory of a repository, which makes monorepos straightforward.

Why it exists. A release that is not in version control cannot be traced, reviewed or rolled back.

Possible results

git repository · possible results
✓ Git repository✗ Not a Git repository

Configure

.shipcheck.yml
checks:  git_repo: false   # turn it offseverity:  git_repo: warn    # or error

Working tree

id
git.clean
config key
git_clean

What it checks. Runs git status and fails if there are uncommitted or untracked changes. Files ignored by .gitignore are ignored here too. --verbose lists the first ten changed paths.

Why it exists. If what you deploy differs from what is committed, the release cannot be reproduced and the fix you forgot to commit will be lost.

Possible results

working tree · possible results
✓ Working tree clean✗ 3 uncommitted changes– Working tree not checked (not a Git repository)

Configure

.shipcheck.yml
checks:  git_clean: false   # turn it offseverity:  git_clean: warn    # or error

Branch

id
git.branch
config key
git_branch

What it checks. Warns when you are not on a branch you ship from. Those branches come from git.protected_branches. Without configuration, Shipcheck uses main or master if either exists in the repository, and skips the check otherwise.

Why it exists. Shipping from a feature branch is a common way to release code that was never reviewed or merged.

Possible results

branch · possible results
✓ On branch main⚠ On branch feature/login, not main– Detached HEAD (no branch to check)

Configure

.shipcheck.yml
git:  protected_branches:    - main    - release

Tests

id
tests
config key
tests

What it checks. Detects and runs the project's test command with CI=true, so watch-mode runners exit after one run. Output is captured, not streamed. On failure, --verbose shows the last 20 lines with secret values redacted.

Why it exists. “Tests pass on my machine” is only true if you actually ran them on the code you are about to ship.

  • Go: go test ./... (go.mod)
  • Node.js: npm test, pnpm run test, yarn run test or bun run test, chosen from packageManager or the lockfile. The default “no test specified” script is ignored.
  • Python: pytest or python -m pytest, when pytest.ini, conftest.py, a tests/ directory or [tool.pytest] exists
  • Java: ./mvnw test or mvn test (pom.xml), ./gradlew test or gradle test (build.gradle)
  • PHP: php artisan test (Laravel), composer test, or vendor/bin/phpunit
  • Rust: cargo test (Cargo.toml)

Possible results

tests · possible results
✓ Tests passed✗ Tests failed (npm test)✗ Tests not run: cargo is not installed– No test command detected

Configure

.shipcheck.yml
commands:  tests: npm run test:ci  timeout: 10m

Production build

id
build
config key
build

What it checks. Detects and runs the project's production build command. The build writes its usual output (for example dist/ or .next/) and nothing else.

Why it exists. Many errors only appear in production mode: type errors, missing imports or environment-specific config.

  • Go: go build ./...
  • Node.js: the build script in package.json, run with your package manager
  • Java: mvn package -DskipTests or gradle assemble (wrappers preferred)
  • Rust: cargo build --release
  • Python and PHP have no standard build step. Set commands.build if you have one.

Possible results

production build · possible results
✓ Production build passed✗ Production build failed (npm run build)✗ npm run build timed out after 10m0s– No build command detected

Configure

.shipcheck.yml
commands:  build:    - npm run build    - go build ./...

Environment

id
environment
config key
environment

What it checks. Reads variable names from .env.example, .env.sample or .example.env, plus environment.required. Each one must have a non-empty value, either in the process environment or in a dotenv file (.env by default). The check also warns when a real .env file is committed to Git.

Why it exists. A missing API key is the classic “works locally, breaks in production” bug. A committed .env is a leaked secret.

  • Only variable names are ever printed. Values stay private.
  • Values of required variables are replaced with **** in any test or build output Shipcheck shows.

Possible results

environment · possible results
✓ Environment checked✗ 2 environment variables missing⚠ .env is committed to Git– No environment variables required

Configure

.shipcheck.yml
environment:  required:    - DATABASE_URL    - API_KEY  files:    - .env    - .env.local

README

id
readme
config key
readme

What it checks. Looks for README.md, case-insensitively. README, README.markdown, README.rst and README.txt also count. An empty README is reported as a warning.

Why it exists. The README is the first thing users, teammates and your future self read.

Possible results

readme · possible results
✓ README found⚠ No README⚠ README.md is empty

Configure

.shipcheck.yml
checks:  readme: false   # turn it offseverity:  readme: warn    # or error

License

id
license
config key
license

What it checks. Looks for LICENSE, LICENSE.md or LICENSE.txt (also LICENCE and COPYING), and names MIT, Apache-2.0, GPL, AGPL, LGPL, MPL-2.0, ISC, BSD and the Unlicense when it recognises them.

Why it exists. Without a license, others cannot legally use your code. For private projects, turn the check off.

Possible results

license · possible results
✓ License found (MIT)⚠ No license file

Configure

.shipcheck.yml
checks:  license: false

Version

id
version
config key
version

What it checks. Reads the version from package.json, Cargo.toml, pyproject.toml, composer.json, pom.xml, build.gradle(.kts) or a VERSION file, falling back to the latest Git tag. If a tag for that version (1.4.0 or v1.4.0) already exists at an older commit than HEAD, it warns.

Why it exists. Shipping new code under an old version number makes bug reports and rollbacks ambiguous.

Possible results

version · possible results
✓ Version 1.4.0⚠ Version 1.4.0 was already released (v1.4.0)⚠ No version found

Configure

.shipcheck.yml
checks:  version: false   # turn it offseverity:  version: warn    # or error

CI

id
ci
config key
ci

What it checks. Detects GitHub Actions (.github/workflows/*.yml), GitLab CI (.gitlab-ci.yml), CircleCI (.circleci/), Jenkins (Jenkinsfile), Bitbucket Pipelines and Azure Pipelines. A missing CI setup is a warning, never a failure, unless you raise its severity.

Why it exists. CI runs your checks on every push, not just when you remember.

Possible results

ci · possible results
✓ CI configured (GitHub Actions)⚠ No CI configuration

Configure

.shipcheck.yml
severity:  ci: error   # require CI

Adding a check

Checks live in cli/internal/checks. Each one is a small value with an ID, a name, a description and a Run function; the runner handles timing, severity and output. See Contributing for a walkthrough.

Found a mistake? Edit this page on GitHub