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✗ Not a Git repositoryConfigure
checks: git_repo: false # turn it offseverity: git_repo: warn # or errorWorking 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 clean✗ 3 uncommitted changes– Working tree not checked (not a Git repository)Configure
checks: git_clean: false # turn it offseverity: git_clean: warn # or errorBranch
- 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
✓ On branch main⚠ On branch feature/login, not main– Detached HEAD (no branch to check)Configure
git: protected_branches: - main - releaseTests
- 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 passed✗ Tests failed (npm test)✗ Tests not run: cargo is not installed– No test command detectedConfigure
commands: tests: npm run test:ci timeout: 10mProduction 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 passed✗ Production build failed (npm run build)✗ npm run build timed out after 10m0s– No build command detectedConfigure
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 checked✗ 2 environment variables missing⚠ .env is committed to Git– No environment variables requiredConfigure
environment: required: - DATABASE_URL - API_KEY files: - .env - .env.localREADME
- 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 found⚠ No README⚠ README.md is emptyConfigure
checks: readme: false # turn it offseverity: readme: warn # or errorLicense
- 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 found (MIT)⚠ No license fileConfigure
checks: license: falseVersion
- 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 1.4.0⚠ Version 1.4.0 was already released (v1.4.0)⚠ No version foundConfigure
checks: version: false # turn it offseverity: version: warn # or errorCI
- 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 configured (GitHub Actions)⚠ No CI configurationConfigure
severity: ci: error # require CIAdding 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