Community · 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 — and 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 — and 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, start in measurement-only mode to baseline before you enforce, and keep everything local: the scan runs on your runner, and no source is ever uploaded.

  • Free — no login, no account
  • pre-commit + 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.

  1. Agent writes code
  2. pre-commit (local)
  3. CI gate (pull request)
  4. 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 integrations, one engine. Start with pre-commit for the fast local loop, then add the CI gate so nothing slips past a skipped hook.

local · pre-commit

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 Community CLI by default, cached per version.
  • Same scan, same rules, same exit code as the CI gate.
shipmoor-pre-commit on GitHub
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]
ci/cd · github actions

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.
CI gating docs
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 Community 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
        run: |
          shipmoor scan \
            --diff "origin/${{ github.base_ref }}...HEAD" \
            --fail-on high --no-color
ci/cd · advanced

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.
name: "Shipmoor Gate"
description: >
  Runs the Shipmoor Community 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
  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 Community 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 }}
      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. No source is uploaded, there is no Shipmoor cloud in the critical path, and the same deterministic engine that fails your build is the one you can run offline on your laptop. A red check you can reproduce in one command beats a verdict you have to trust.

Free in Community, deeper in IC

The CI Gate is a Community feature — the structural scan and the gate need no login. The paid IC tier adds the Claim Check: did the change actually do what the task asked?

  • Community · free

    CI Gate

    Structural AI-code-integrity gate, everywhere your code moves.

    • pre-commit hook + any CI (GitHub Actions, GitLab, Jenkins…)
    • phantom imports, hallucinated APIs, stub paths, placeholders
    • diff-scoped gating + SARIF 2.1.0 output
    • no login, no account, no source upload
  • Shipmoor IC

    Claim Check in CI

    Add an intent gate on top of the structural one.

    • checks the change against the task it was given
    • deterministic probes, optional BYO-Judge second opinion
    • gates on falsifiable evidence only
    • same local-first, no-source-upload guarantee

Put the gate in front of your next agent change

Install the free CLI, drop the pre-commit hook and the CI job in, and let the build fail on the defects agents introduce — before they reach review.

Get Shipmoor CLI

One installer. One shipmoor command. Free Community scans.

curl -fsSL https://dl.shipmoor.dev/install.sh | bash


CI gating docs

CI Gate FAQ FAQ

Pre-commit, CI, severity gates, and rolling it out without breaking every build.

Contact sales

Our team can help with custom support, team rollouts, and self-hosted deployments. Or to get started now, explore our self-serve plans.