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:
| Verdict | Exit code |
|---|---|
READY | 0 |
READY WITH GAPS | 0 |
BLOCKED | 1, always |
INCONCLUSIVE | 1, 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. ABLOCKEDhere 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 tocannot_check. The judge can onlyBLOCKby citing a divergence, orABSTAIN; it never turns a floor result back into a pass, and it never producesREADYon its own. But a confirmed judgeBLOCKis a realBLOCKEDverdict, and a required-judgmentABSTAINis a realINCONCLUSIVE. 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-onlyensures no judge runs: no model call, no secret, nothing but the deterministic floor against the frozen acceptance set.--intent-ticketand--intent-promptsupply 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-outwrites 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
- The three-layer model this page assumes: What is Claim Check.
- The verdict and exit-code model in full: CLI reference.
- Why the judge can only
BLOCKorABSTAIN, never pass an obligation: BYO-Judge. - What can and cannot cause a
BLOCKEDresult at the engine level: Turning on the gate. - The deterministic scan gate in GitHub Actions: GitHub Actions.
- Wiring test runs into the same verdict: Using Test Evidence with Claim Check.