Free · CI Gate
Gate AI-code defects before they merge
The Shipmoor CI Gate runs the same deterministic scan in two places: as a pre-commit hook on your laptop and as a CI job on every pull request. It fails the build when a finding at or above your severity threshold is present. Phantom imports, hallucinated APIs, stub paths, and placeholder bodies stop at the gate instead of in review.
A coding agent finishes a task, writes a confident summary, and opens a pull request. The diff compiles and the tests it wrote pass, but it imports a module that doesn’t exist, calls an API the library never shipped, or leaves a handler body as a TODO that quietly returns None. These are AI-code-integrity defects: plausible-looking mistakes that survive type checks and land in review, where a human has to catch them by reading carefully.
The Shipmoor CI Gate stops them earlier. It runs one deterministic scan in two places: as a pre-commit hook on the developer’s machine and as a CI job on every pull request. It fails the build when a finding at or above your severity threshold is present. Scope it to the change so pre-existing debt never blocks, and start in measurement-only mode to baseline before you enforce. The scan runs on your runner; source code and diffs are not uploaded to Shipmoor.
- Free account · Local execution · No source upload
- pre-commit and any CI
- Runs on your runner · no source upload
- pre-commit
- GitHub Actions
- SARIF 2.1.0
- exit-code gate
- diff-scoped
- pinned CLI
- no source upload
$ shipmoor scan --diff origin/main...HEAD --fail-on high --no-color Scanning 7 changed files (origin/main...HEAD) HIGH phantom-import services/api/handlers/webhook.py:12 import stripe.webhooks_v2 — module does not existHIGH stub-path services/api/handlers/webhook.py:41 handler returns None — TODO body never implemented Gate: FAILED — 2 high findings introduced by this changeSARIF written to shipmoor.sarif · exit 1 The gate on a pull request: scan the introduced change, fail the job on a high-severity finding.
One scan, two gates
The gate is the same binary and the same rules wherever it runs. Pre-commit catches defects locally, before they ever reach the remote; CI is the backstop that can't be skipped. Nothing pre-existing has to block. Scope the gate to the change.
- Agent writes code
- pre-commit (local)
- CI gate (pull request)
- Review & merge
Catch it on the laptop where it's a one-line fix, or at the pull request where it's still cheaper than a review round-trip. Either way the same finding can't make it to main.
Wire the gate into your workflow
Two Scan integrations, one engine. Start with pre-commit for the fast local loop, then add the token-authenticated CI gate so nothing slips past a skipped hook. For reading-level feedback, run Code Review locally: Free includes 5 Reviews per month, and every review stays advisory.
Pre-commit hook
The official pre-commit repo installs the Shipmoor CLI for you and scans the staged change. Latest CLI by default; pin a version with args.
- Run pre-commit install once; the hook runs on every commit.
- Installs the latest Shipmoor CLI by default, cached per version.
- Same scan, same rules, same exit code as the CI gate.
repos:
- repo: https://github.com/shipmoor/shipmoor-pre-commit
rev: v0.1.0
hooks:
- id: shipmoor-scan
# optional: pin the CLI and the severity gate
args: [--shipmoor-version=0.3.0, --fail-on, high]
GitHub Actions: quickstart
A minimal pull-request gate: install the pinned CLI from the official installer, scan only what the change introduced, and fail on a high finding.
- fetch-depth: 0 so the diff base is available for diff-scoped scans.
- Pin SHIPMOOR_VERSION for reproducible builds; scan . for a full tree.
- Exit code is the gate; non-zero at or above --fail-on fails the job.
name: shipmoor
on:
pull_request:
permissions:
contents: read
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # the diff needs history
- name: Install Shipmoor CLI (pinned)
env:
SHIPMOOR_VERSION: "0.3.0"
SHIPMOOR_NO_PATH_HINT: "1"
run: |
curl -fsSL https://dl.shipmoor.dev/install.sh | bash
echo "$HOME/.shipmoor/bin" >> "$GITHUB_PATH"
- name: Gate the change
env:
SHIPMOOR_TOKEN: ${{ secrets.SHIPMOOR_TOKEN }}
run: |
shipmoor scan \
--diff "origin/${{ github.base_ref }}...HEAD" \
--fail-on high --no-color
Authorize self-managed Scan
Create a revocable, Scan-scoped machine token and store it as the SHIPMOOR_TOKEN secret. Current self-managed CI supports Scan only; Review, Claim Check, Test Evidence, and Blast Radius remain local commands.
- Machine tokens are Scan-scoped, revocable, and optionally expiring.
- The CLI reads the token from SHIPMOOR_TOKEN and never copies it to disk.
- Review and the other metered commands run locally; managed CI is coming with Team.
# Create this once on an authorized development machine.
shipmoor auth token create --name github-my-repo --scope scan
# Save the displayed secret as the repository secret SHIPMOOR_TOKEN.
# The workflow exposes it only to the Shipmoor Scan step.
GitHub Actions: full composite action
Drop this in as a local action for the complete setup: a pinned install, diff-scoped gating, a measurement-only baseline mode, and SARIF + Markdown reports uploaded as artifacts on every run. Call it with uses: ./.github/actions/shipmoor-gate.
- diff-base gates only findings the change introduced. Pre-existing debt never blocks.
- soft-fail: true is the measurement-only baseline: it reports without failing.
- Always uploads SARIF 2.1.0 + a Markdown summary as artifacts, even on failure.
- Per-package config-file makes it monorepo-ready out of the box.
- Pass a Scan-scoped machine token from secrets as the token input.
name: "Shipmoor Gate"
description: >
Runs the Shipmoor CLI against scan-path to catch AI-code-integrity
defects (phantom imports, hallucinated APIs, stub paths, placeholder bodies).
Installs the pinned CLI via the official installer, scans, and fails the
build when a finding at or above `fail-on` severity is present. Always
uploads SARIF 2.1.0 + a Markdown summary as artifacts. Set soft-fail: true
for the measurement-only baseline run.
inputs:
scan-path:
description: "Directory to scan (e.g. services/realtime-listing-processor)"
required: true
token:
description: "Scan-scoped Shipmoor machine token"
required: true
version:
description: "Pinned Shipmoor CLI version (SHIPMOOR_VERSION)"
required: false
default: "0.3.0"
fail-on:
description: "Severity gate: none | medium | high | critical"
required: false
default: "high"
config-file:
description: "Path to .shipmoor.yaml relative to scan-path"
required: false
default: ".shipmoor.yaml"
soft-fail:
description: "Report findings without failing the job (true/false)"
required: false
default: "false"
diff-base:
description: >
When set, gate only findings introduced relative to this git ref by
running `shipmoor scan --diff <diff-base>...HEAD` (honours
.shipmoor.yaml `diff.only_introduced`). Leave empty for a full-tree scan.
Typically the PR base SHA: pre-existing debt must not block.
required: false
default: ""
runs:
using: "composite"
steps:
- name: Install Shipmoor CLI (pinned)
shell: bash
env:
SHIPMOOR_VERSION: ${{ inputs.version }}
SHIPMOOR_NO_PATH_HINT: "1"
run: |
curl -fsSL https://dl.shipmoor.dev/install.sh | bash
echo "$HOME/.shipmoor/bin" >> "$GITHUB_PATH"
- name: Show Shipmoor version
shell: bash
run: shipmoor version
- name: Resolve artifact suffix
id: name
shell: bash
run: |
SAFE="$(echo "${{ inputs.scan-path }}" | tr '/ ' '--')"
echo "suffix=$SAFE" >> "$GITHUB_OUTPUT"
- name: Run Shipmoor scan
shell: bash
working-directory: ${{ inputs.scan-path }}
env:
SHIPMOOR_TOKEN: ${{ inputs.token }}
run: |
SARIF="$RUNNER_TEMP/shipmoor-${{ steps.name.outputs.suffix }}.sarif"
SUMMARY="$RUNNER_TEMP/shipmoor-${{ steps.name.outputs.suffix }}.md"
# In soft-fail (baseline) mode the gate never blocks, so scan with
# --fail-on none; otherwise honour the requested severity threshold.
if [ "${{ inputs.soft-fail }}" = "true" ]; then
FAIL_ON="none"
else
FAIL_ON="${{ inputs.fail-on }}"
fi
# Prefer the per-package config, fall back to the repo-root config,
# otherwise let Shipmoor auto-discover.
CONFIG_ARG=""
if [ -f "${{ inputs.config-file }}" ]; then
CONFIG_ARG="--config ${{ inputs.config-file }}"
elif [ -f "$GITHUB_WORKSPACE/.shipmoor.yaml" ]; then
CONFIG_ARG="--config $GITHUB_WORKSPACE/.shipmoor.yaml"
fi
# When a diff base is supplied, gate only findings introduced by the
# change (legacy debt must not block migrations). Empty => full scan.
DIFF_ARG=""
if [ -n "${{ inputs.diff-base }}" ]; then
DIFF_ARG="--diff ${{ inputs.diff-base }}...HEAD"
echo "Shipmoor diff scope: ${{ inputs.diff-base }}...HEAD"
else
echo "Shipmoor scope: full tree"
fi
EXIT=0
shipmoor scan . \
--sarif --output "$SARIF" \
--markdown-summary "$SUMMARY" \
--fail-on "$FAIL_ON" \
--no-color \
$DIFF_ARG \
$CONFIG_ARG || EXIT=$?
if [ "${{ inputs.soft-fail }}" = "true" ]; then
echo "soft-fail mode: shipmoor exit was ${EXIT}, not failing the job"
exit 0
fi
exit "${EXIT}"
- name: Upload Shipmoor report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: shipmoor-report-${{ steps.name.outputs.suffix }}
path: |
${{ runner.temp }}/shipmoor-${{ steps.name.outputs.suffix }}.sarif
${{ runner.temp }}/shipmoor-${{ steps.name.outputs.suffix }}.md
if-no-files-found: warn
Tune the gate
Every behavior in the action maps to a flag on shipmoor scan — the same flags you run locally.
-
Gate only what the PR introduced
shipmoor scan --diff origin/main...HEAD --fail-on high -
Set the severity threshold
shipmoor scan --staged --fail-on high # none | medium | high | critical -
Measurement-only baseline (never blocks)
shipmoor scan . --fail-on none --sarif --output shipmoor.sarif -
Per-package config in a monorepo
shipmoor scan services/api --config services/api/.shipmoor.yaml -
Reproduce the exact CI gate locally
SHIPMOOR_VERSION=0.3.0 shipmoor scan --staged --fail-on high
Same binary, same rules, same exit code, whether it runs in pre-commit, in CI, or on your laptop.
The gate runs in your pipeline, not ours
The official installer drops a pinned binary onto your runner, the scan runs there, and the job's exit code is the gate. Your source code and diffs are not uploaded. Shipmoor contacts the service for authentication, entitlements, command allowances, and features you explicitly enable. The same deterministic engine runs locally and in CI.
Free for local and self-managed CI
A new personal account receives one 30-day period of full Pro at account creation, with no card or invitation required. Free keeps Scan, Agent Harness, and Agent Skills unlimited and includes separate monthly allowances for the four verification commands.
- $0
Free
Useful recurring verification for local work and self-managed CI.
- 5/month each: Claim Check, Review, Test Evidence, Blast Radius
- Unlimited Scan, Agent Harness, and Agent Skills
- Pre-commit and self-managed CI with SARIF
- Account required; source and diffs are not uploaded
- $19 / month
Pro
Unlimited individual verification and the full advanced workflow.
- Unlimited Claim Check, Review, Test Evidence, and Blast Radius
- Unlimited Scan, Agent Harness, and Agent Skills
- Full IDE, advanced configuration, and richer local history
- Local execution; source and diffs are not uploaded
Put the gate in front of your next agent change
Create your personal account to receive one 30-day period of full Pro with no card, authorize the CLI, and put the deterministic Scan in pre-commit or token-authenticated CI.
CI Gate FAQ FAQ
Pre-commit, CI, severity gates, and rolling it out without breaking every build.
Keep exploring
- Shipmoor CLI The local scanner the gate is built on.
- AI code integrity The category the gate enforces.
- Claim Check Gate on whether the change did what was asked.
- Code Review Run the advisory reading companion locally with your own agent.
- How the scan works The deterministic engine, stage by stage.
- Pricing Free, Pro, Team, and Enterprise.