CI integration

Shipmoor Team
July 13, 2026
9 min read

Claim Check runs the same way in CI as it does on your laptop: same binary, same acceptance set, same diff, same verdict. Nothing about CI is a special mode. What differs in CI is what you do with the exit code, and that decision is narrower than it looks.

The output is a VSA (Verification Summary Attestation), written to .shipmoor/claim-check.vsa.json by default or to a path you choose with --vsa-out. Upload it as a build artifact so a reviewer, or anything downstream, can see exactly what was checked and what was not. See Attestation artifacts for the full shape.

The exit code is not opt-in

shipmoor claim-check maps its verdict straight to an exit code, and that mapping is not something you switch on:

VerdictExit code
READY0
READY WITH GAPS0
BLOCKED1, always
INCONCLUSIVE1, by default

There is no flag that softens this. If a CI step runs shipmoor claim-check and something checks its exit code, a BLOCKED or an INCONCLUSIVE result fails that step the moment the step exists. There is nothing further to configure to make that happen.

So the question a CI author actually has to answer isn’t “should this gate a merge.” It’s: what is allowed to produce a BLOCKED or INCONCLUSIVE result, in the step whose exit code I trust?

Floor-only or full mode: the real decision

Claim Check has two modes, and both follow the exit-code mapping above identically. What differs between them is what can cause that result in the first place.

  • --floor-only: the deterministic floor decides, alone, no model and no secret. It produces a reproducible verdict from a frozen acceptance set and a fixed evidence set. A BLOCKED here means the floor caught a required acceptance criterion that didn’t earn its claim: a mechanical check came back unsatisfied. This is the step most teams should be comfortable making required.
  • Full mode (drop --floor-only, add --agent): the floor runs first, then the judge adjudicates the acceptance criteria the floor routed to cannot_check. The judge can only BLOCK by citing a divergence, or ABSTAIN; it never turns a floor result back into a pass, and it never produces READY on its own. But a confirmed judge BLOCK is a real BLOCKED verdict, and a required-judgment ABSTAIN is a real INCONCLUSIVE. Both fail the build if this is the step whose exit code you trust.

Putting the judge in your required step is a legitimate choice. It’s a different choice from running the floor alone, and it should be made on purpose, not by accident of which command happened to land in the YAML.

A minimal floor-only gate

shipmoor claim-check . --floor-only \
  --intent-ticket "$INTENT_TICKET_PATH" \
  --intent-prompt "$PROMPT" \
  --vsa-out .shipmoor/claim-check.vsa.json
  • --floor-only ensures no judge runs: no model call, no secret, nothing but the deterministic floor against the frozen acceptance set.
  • --intent-ticket and --intent-prompt supply the intent; either can be a literal string or a path to a file. A checked-in ticket file paired with the prompt that produced the change is the strongest combination.
  • --vsa-out writes the VSA next to the result. Upload it as an artifact.
  • The exit code decides the build. No further flag is needed to make that so.

Other selectors fit other triggers: --staged for a pre-commit-style check, --diff <spec> for an arbitrary range, --all to check the whole target instead of just a diff. The full set, with defaults and precedence, is in the CLI reference.

GitHub Actions: floor as the required check

name: shipmoor-claim-check
on:
  pull_request:
permissions:
  contents: read
jobs:
  claim-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0     # full history for the merge-base diff

      - name: Install Shipmoor CLI
        run: |
          curl -fsSL https://dl.shipmoor.dev/install.sh | bash
          echo "$HOME/.shipmoor/bin" >> "$GITHUB_PATH"

      - name: Shipmoor Claim Check (deterministic floor, this step is the gate)
        run: |
          shipmoor claim-check . --floor-only \
            --from "origin/${{ github.event.pull_request.base.ref }}" \
            --to "${{ github.event.pull_request.head.sha }}" \
            --intent-ticket "${{ vars.SHIPMOOR_INTENT_TICKET_PATH }}" \
            --intent-prompt "${{ vars.SHIPMOOR_PROMPT }}" \
            --vsa-out .shipmoor/claim-check.vsa.json

      - name: Upload VSA attestation
        if: ${{ always() }}
        uses: actions/upload-artifact@v4
        with:
          name: shipmoor-claim-check-vsa
          path: .shipmoor/claim-check.vsa.json
          if-no-files-found: warn

The shipmoor-claim-check job’s exit code is the whole gate: no continue-on-error, no separate policy step to wire up. The VSA is uploaded if: always() so you still get the attestation on a failing change, which is the most useful moment to inspect it.

Generic CI

The same pattern works in any CI. Run the floor-only command and let its exit code decide the build; upload the VSA however your CI stores artifacts:

curl -fsSL https://dl.shipmoor.dev/install.sh | bash
export PATH="$HOME/.shipmoor/bin:$PATH"

shipmoor claim-check . --floor-only \
  --intent-ticket "$INTENT_TICKET_PATH" \
  --intent-prompt "$PROMPT" \
  --vsa-out .shipmoor/claim-check.vsa.json

# A non-zero exit here means BLOCKED or INCONCLUSIVE.
# Upload .shipmoor/claim-check.vsa.json as a build artifact either way.

Rolling out the required check without surprising your team

There’s no preview flag that computes the verdict “as if” the step were required while always exiting 0. If you want to see what a change would report before it can fail anyone’s build, do that at the CI-configuration level instead of the CLI level: run the floor-only step for a trial period as a non-required check, or with continue-on-error: true, and read the VSA and the exit code on real pull requests. Only mark the check required in your branch protection settings once you trust what it reports. The command’s behavior is identical during the trial and after; only whether your CI is allowed to block a merge on it changes.

Bringing the judge into the required check (optional)

If you want the judge’s opinion to carry the power to fail a merge, drop --floor-only and add --agent in the same required step:

shipmoor claim-check . \
  --agent claude \
  --intent-ticket "$INTENT_TICKET_PATH" \
  --intent-prompt "$PROMPT" \
  --vsa-out .shipmoor/claim-check.vsa.json

claude is the only bare preset with a built-in headless invocation; point --agent at a raw command (for example your own wrapper around another coding-agent CLI) to use anything else.

A confirmed judge BLOCK is a BLOCKED verdict like any other; a judge ABSTAIN on a required acceptance criterion is INCONCLUSIVE. Both fail this step’s exit code, same as a floor miss, because the exit-code mapping above does not distinguish where the verdict came from.

Running the judge as a separate, non-blocking step instead

Most teams want the judge’s opinion visible without giving it the power to fail a build on its own. Do that by running full mode in a second step that is if: always() and || true-guarded, writing to its own artifact:

      - name: Shipmoor Claim Check (judge, advisory only)
        if: ${{ always() }}
        env:
          SHIPMOOR_AGENT: ${{ vars.SHIPMOOR_LOCAL_AGENT }}
        run: |
          if [[ -n "${SHIPMOOR_AGENT}" ]]; then
            shipmoor claim-check . \
              --from "origin/${{ github.event.pull_request.base.ref }}" \
              --to "${{ github.event.pull_request.head.sha }}" \
              --agent "${SHIPMOOR_AGENT}" \
              --json > shipmoor-claim-check-advisory.json \
              2> shipmoor-claim-check.stderr || true
          else
            echo '{"schema_version":"shipmoor.scan.v1","change_results":[]}' > shipmoor-claim-check-advisory.json
          fi

      - uses: actions/upload-artifact@v4
        with:
          name: shipmoor-claim-check-advisory
          path: shipmoor-claim-check-advisory.json
          if-no-files-found: warn

The || true is what makes this step advisory: whatever the judge decides, it cannot flip this job’s conclusion, because this step’s own exit code is discarded. Pair this with the required floor-only step above: the floor decides pass or fail, and the judge’s opinion shows up in an artifact for a reviewer to read.

For fork PRs, follow the same split-workflow trust model as Code Review CI: the required floor-only step already needs no secret, so it’s safe to run on PR head as-is. If your judge needs a provider key, run it in a separate, trusted post job that never checks out PR head.

Composing Test Evidence

A pipeline can also run shipmoor test-evidence as its own step. When the claim_check entitlement is active, Claim Check automatically composes that output into its verdict rather than treating it as a separate signal to reconcile by hand. The strongest version of this wires the agent’s PostToolUse hook upstream, in the coding agent’s own environment, so a test run is captured at the moment the agent produces it rather than reconstructed later in CI. See Using Test Evidence with Claim Check for the full mechanics; this page only covers the CI wiring for Claim Check itself.

Capability gating (claim_check) in CI

Full mode is gated on the claim_check entitlement. Check it with a source-free probe:

shipmoor capabilities --json | jq -r '.capabilities.claim_check.enabled'

If claim_check is not enabled, full mode is unavailable and the judge is skipped. Whether floor-only mode itself requires the entitlement is still being settled; if your CI needs a definite answer for your account, check shipmoor capabilities --json directly or see Plans & tiers.

Secrets and egress notes

  • Floor-only mode needs no secret and makes no network call. It’s safe to run on any runner, including fork PRs.
  • Bring-your-own-agent provider keys come from Actions secrets only, injected into the agent command’s own env. The only judge-time network egress is your agent’s own provider call. The Shipmoor binary itself makes no judge-time network call. On a locked-down runner, allowlist only your agent’s provider endpoint.
  • The VSA contains no source, no diffs, no raw intent text, and no model rationale. It’s safe to publish as an artifact. See Privacy & telemetry.

See also

Last updated on July 13, 2026

Was this article helpful?

Your response is saved on this device.

Related Articles