Ship Check: one honest verdict before you deploy
Ship Check runs all four TechFix checks on your project, merges what they find, and flags the risks that only show up when two findings meet. The first line tells you SAFE TO SHIP, NOT YET with counts, or UNVERIFIED when something could not be checked.
Free during launch. Works with Claude Code, Claude.ai, Cursor and Codex.
When to use it
Use Ship Check right before you deploy, launch, open-source or announce something. It is for the moment you would otherwise ask yourself:
- "Is this safe to ship?"
- "Can I deploy this tonight, or will something break, leak or run up a bill?"
- "Anything I should fix before going live?"
- "My agent wrote most of this. Did it put a key somewhere it shouldn't?"
- "I want one check in CI that fails the PR when a key leaks or a test fails."
Type /ship-check, or just ask "is this safe to ship?" or "is it ready to launch?". From a terminal or CI: python3 ship-check/scripts/ship_check.py path/to/repo.
Ship Check runs the other four TechFix checks in one go, looks at where their findings meet, and puts one verdict on the first line: SAFE TO SHIP, NOT YET (with counts of what will break, leak or cost you), or UNVERIFIED (with what could not be checked).
How it works
techfix-<name> also works). If one is missing, Ship Check says which one, runs the rest, and that area counts as "not checked".- It runs the four scans. Broke After Deploy, Key Leaked and Bill Spiked scan the folder (and Key Leaked the git history). Prove It Works runs in ship mode: your tests, type check and production build only, each bounded by a timeout (300 seconds per command by default). After a build, Key Leaked scans the fresh build output again.
- It merges the results. A line found by two skills becomes one item with both labels. Identical hits (same skill, rule, provider and folder) become one item that lists every place. The counts in the verdict are items, not raw hits.
- It applies the cross-checks. Six rules for problems that only show up when two findings meet, like a leaked AI key next to an uncapped call to the same provider. These are marked CRITICAL, HIGH or BLOCKER and listed first.
- It gives one verdict on the first line, then "Do these first": up to three actions with item numbers. Then every finding, grouped as Will leak, Will cost you, Will break and Not proven, each with Finding, Evidence (file and line), Impact and Fix.
- Claude does a review pass with the review checklist: opens every confident item to confirm it, drops look-alikes and recounts, walks each sibling skill's own checklist for what patterns miss, confirms or drops the cross-checks it can settle by reading or by asking you (was the site deployed with that value set? is there auth in a gateway?), and notes links between findings the rules don't cover.
- Fixes come in order, one diff at a time, applied only with your approval: rotate leaked keys first, then spending caps and code limits, then deploy breakers, then failing checks, then rerun. Rotation and dashboard caps happen outside your code, so you get the exact steps. The scans are read-only; the only things that write are your project's own test and build commands (skip them with
--no-tests) and, if you OK it, one line per run in.techfix/ledger.md.
The three verdicts
| First line | When | Exit code |
|---|---|---|
SAFE TO SHIP | All four skills ran with complete coverage, at least one test or build passed, nothing failed and nothing blocking was found. It may add "plus N to check". | 0 |
NOT YET: N will break, N leak, N will cost you | Any confident leak, cost or break item, any failing test, type check or build, or a scanner that could not run (missing, crashed, timed out, output not recognised). Adds "(N critical cross-checks)" and "N failing checks" when they apply. | 1 |
UNVERIFIED: <what was not checked> | Nothing blocking was found, but something could not be checked: tests not run (--no-tests, dependencies not installed, a timeout), no test or build passed, a shallow git clone, a file too big to scan, a stack the deploy checks do not cover. It is never shown as safe, and it says how to close each gap. | 3 |
| (Ship Check itself failed) | The tool hit an error of its own. | 2 |
A check that times out, or fails only because a dependency is not installed, is "not run", which makes the verdict UNVERIFIED. A real test failure is NOT YET. For SAFE TO SHIP you need the project's dependencies installed and the full git history.
Using it in CI
The script runs without Claude, so you can put it in a GitHub Actions job. --ci adds the report to the job summary, and --fail-on picks which NOT YET findings fail the job (leak, cost, break, failing, critical; default: all). A skill that could not run always fails the job, and UNVERIFIED exits 3.
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history, or old commits cannot be scanned for keys (UNVERIFIED)
- run: npm ci # install dependencies, or tests are NOT RUN (UNVERIFIED)
- run: python3 .claude/skills/ship-check/scripts/ship_check.py . --ci --fail-on leak,failing
Be honest with yourself about what CI gives you. In CI there is no Claude review pass, so the verdict counts the scanners' raw hits. On large monorepos most raw "will break" hits were look-alikes that the review pass drops (localhost fallbacks, email preview props, optional variables). Leak hits and failing checks were far more reliable. So gate the job on --fail-on leak,failing, treat the other groups as a to-do list, and run /ship-check in Claude for the reviewed verdict.
What it checks
Will leak (Key Leaked)
- Secrets in your code, your git history and the JavaScript you ship to browsers.
- Secrets behind a browser prefix (
NEXT_PUBLIC_,VITE_and similar) that the build inlines into public JavaScript. - Secrets in the build output, scanned again right after Ship Check runs your build.
Will cost you (Bill Spiked)
- Paid AI calls with no output limit, whole chat histories resent on every call.
- Public endpoints that call a paid API with no sign-in or no rate limit.
- Loops, retries and timers that multiply cost; SMS or email sends with no per-user limit.
- A spending-cap checklist for each paid provider and host it finds, sourced and dated, saying whether the cap is a hard cap.
Will break (Broke After Deploy)
- Localhost URLs, missing production env vars, files the server can't write, imports that only work on your laptop, and more.
- JavaScript and TypeScript apps in full; Python web apps (Django, FastAPI, Flask) with basic checks. Other stacks are reported as not covered.
Not proven (Prove It Works, ship mode)
- Your real tests, type check and production build, with the real output. Commands that deploy, publish, migrate or install are never run automatically.
Cross-checks (only in the bundle)
- CC1, CRITICAL: a leaked or browser-exposed paid key plus an uncapped or public call to the same provider. Anyone can run up that bill.
- CC2, HIGH: a secret in browser code plus a project that deploys to a host. Treat it as exposed now, so rotation comes first.
- CC3, CRITICAL: a public endpoint with no sign-in and no rate limit that calls a paid API.
- CC4, HIGH: an env var missing in production that is the same key that leaked. Rotate first, then set the new value (don't paste the leaked one into your dashboard).
- CC5, BLOCKER: a failing test, type check or build. NOT YET, whatever else the scan found.
- CC6, CRITICAL: a secret inside the build output your host serves.
What it can't see
- Hosting dashboards: which env vars are set for production, spending caps, rate limits, key restrictions.
- Whether a leaked key is still live or was used.
- Auth or rate limits outside the repo, such as an API gateway or host firewall.
- Real traffic, live databases, CI-only differences, flaky tests, and anything outside the folder.
Worked examples
1. A chatbot where three small problems add up to one big one
A Next.js chatbot that deploys to Vercel. A client component calls OpenAI directly with a NEXT_PUBLIC_ key, and the server route /api/chat calls gpt-4o with no sign-in, no rate limit and no output limit. Each finding alone is a to-do. Together they mean anyone can run up the OpenAI bill.
"use client";
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.NEXT_PUBLIC_OPENAI_API_KEY,
dangerouslyAllowBrowser: true,
});
app/api/chat/route.ts
export async function POST(req: Request) {
const { messages } = await req.json();
const completion = await openai.chat.completions.create({
model: "gpt-4o",
messages,
});
.env.local (gitignored, value masked)
NEXT_PUBLIC_OPENAI_API_KEY=sk-pro…Z7LJ
OPENAI_API_KEY=sk-pro…Z7LJ
**NOT YET: 0 will break, 1 leak, 3 will cost you (2 critical cross-checks)** Do these first: 1. Rotate the leaked key (#1) at the provider; removing it from code does not un-leak it. 2. Set the provider spending cap, then add sign-in, rate limits and output limits (#1, #2, #3, #4). Repo: chatbot-vercel · 4 of 4 skills ran · 0.9s · history: 1 commit scanned · commit 4595d438d6 … ## Cross-checks (what no single skill sees) 1. **CRITICAL CC1: Anyone can run up your OpenAI bill** (items #1, #2, #3, #4) - Why: The OpenAI key is public (or will be once this ships), and the code shows this account makes uncapped or public OpenAI calls, so nothing in the code or the account stops someone else's usage from becoming your bill. - Fix: Rotate the OpenAI key now (revoke and reissue); deleting it from the code does not un-leak it. - Fix: Set the provider's spending cap or kill switch today: Hard cap: Yes: a monthly hard spend limit on the organization or a project makes affected requests fail with 429 (checked 2026-10-13). - Fix: Then fix the uncapped calls listed (output limits, sign-in, rate limits). 2. **CRITICAL CC3: Open paid endpoint: POST /api/chat** (items #2) - Why: No sign-in and no rate limit in front of a paid call means one script can call it as fast as the network allows, and every call is billed to you. - Fix: Require a signed-in user before the paid call, or put a per-IP rate limit and a bot check on it. - Fix: Add a small output limit (max_tokens) and a daily quota per user. 3. **HIGH CC2: Secret in browser code, and this project deploys to vercel: treat it as exposed** (items #1) - Why: Key Leaked marks a browser-prefixed secret 'at risk' when it sees no build output and no linked host. The host (vercel, from broke-after-deploy) means any build made with this value set bundled it into public JavaScript. - Fix: Ask: was the site deployed with this value set? If yes (or unsure), rotate the key first. - Fix: Remove the browser prefix and read it only in server code. … ## Will leak (1) ### #1 OpenAI project API key (sk-pro...Z7LJ) **CRITICAL** Found by: key-leaked at risk #6,7 + bill-spiked #16 · cross-checks CC2, CC1 - **Status:** Treat as exposed: Key Leaked rated it at risk, but this project deploys to vercel (broke-after-deploy), so any deploy made with this value set put it in public JavaScript. - **Finding:** OpenAI project API key in .env.local and 1 more place: behind a browser-exposed env prefix, so the build bundles it into client JavaScript; used from browser code to call a paid API. … ## Will cost you (3) ### #2 Public endpoint calls OpenAI with no sign-in or rate limit **CRITICAL** Found by: bill-spiked #4,5 · cross-checks CC1, CC3 - **Finding:** app/api/chat/route.ts:6 (POST /api/chat) calls an LLM (OpenAI gpt-4o at app/api/chat/route.ts:8) and anyone on the internet can call it: there is no authentication check and no rate limit. … ### #3 No output limit on gpt-4o call **CRITICAL** Found by: bill-spiked #1 · cross-checks CC1 - **Finding:** app/api/chat/route.ts:8 calls gpt-4o with no output limit (no max_tokens or equivalent), so each reply can run to the model's maximum. Reached by anyone on the internet via POST /api/chat (app/api/chat/route.ts:6). … ### #4 Whole conversation history sent on every call **HIGH** Found by: bill-spiked #7 · cross-checks CC1 - **Finding:** app/api/chat/route.ts:8 sends the full message list the client sends (req.json().messages) to gpt-4o on every call, with no window or truncation. Each new turn re-bills every earlier turn, so cost grows with conversation length, and a client can send any size it likes. Reached by anyone on the internet via POST /api/chat (app/api/chat/route.ts:6). …
What the skill tells Claude to do next: copy the first line exactly, then "Do these first", then confirm each item by opening the line. Here that means reading components/Assistant.tsx:6 (the key really is read in browser code) and app/api/chat/route.ts:6-11 (no auth check, no limiter, no max_tokens), and looking for auth in a layer the scan did not see, such as a middleware.ts. There is none in this repo, so CC3 stands.
For CC2, Claude asks you: was the site ever deployed with NEXT_PUBLIC_OPENAI_API_KEY set? If yes or unsure, rotate. It also points out that OPENAI_API_KEY in the same file holds the same value (both end in Z7LJ), so the server key needs the new value too. It refers to the key only by provider and last 4 characters.
Fix order per the checklist: rotate the OpenAI key at the provider, set the OpenAI monthly hard limit (the output quotes it, with its date), then the code fixes below, one diff at a time, applied only with your approval. On a copy with these changes and the browser variable removed from the env files, the script's first line became SAFE TO SHIP. Rotation still has to happen at OpenAI: removing a key from code does not un-leak it.
components/Assistant.tsx -import OpenAI from "openai"; -const openai = new OpenAI({ - apiKey: process.env.NEXT_PUBLIC_OPENAI_API_KEY, - dangerouslyAllowBrowser: true, -}); … - const r = await openai.chat.completions.create({ model: "gpt-4o-mini", max_tokens: 300, messages: [{ role: "user", content: q }] }); - setAnswer(r.choices[0].message.content ?? ""); + const r = await fetch("/api/chat", { method: "POST", body: JSON.stringify({ messages: [{ role: "user", content: q }] }) }); + const data = await r.json(); + setAnswer(data.reply?.content ?? ""); app/api/chat/route.ts +import { auth } from "@/auth"; +import { ratelimit } from "@/lib/ratelimit"; … export async function POST(req: Request) { + const session = await auth(); + if (!session?.user) return NextResponse.json({ error: "sign in" }, { status: 401 }); + const { success } = await ratelimit.limit(session.user.id); + if (!success) return NextResponse.json({ error: "slow down" }, { status: 429 }); const { messages } = await req.json(); const completion = await openai.chat.completions.create({ model: "gpt-4o", - messages, + max_tokens: 800, + messages: messages.slice(-20), }); .env.local, .env.example -NEXT_PUBLIC_OPENAI_API_KEY=…
2. Nothing leaks, nothing costs, and it still says NOT YET
A small Node notes server. No keys, no paid calls, no deploy problems. But one test fails: the slug function keeps punctuation. The scanners find nothing, and Ship Check still blocks, because a failing test is direct evidence the code does not work (cross-check CC5).
src/slug.js and test/slug.test.jsfunction slugify(title) {
return title.toLowerCase().replace(/ /g, "-");
}
test("drops punctuation", () => {
assert.strictEqual(slugify("Hello, World!"), "hello-world");
});
**NOT YET: 0 will break, 0 leak, 0 will cost you, 1 failing check** Do these first: 1. Make the failing check pass (#1); fix the code, not the test. Repo: notes-failing · 4 of 4 skills ran · 0.8s · history: 1 commit scanned · commit 7e7acb505f … ## Cross-checks (what no single skill sees) 1. **BLOCKER CC5: 1 check failing: NOT YET regardless of everything else** (items #1) - Why: A failing test, type check or build means what you ship is not proven to work, even if nothing else was found. - Fix: Fix the code until the checks pass, then run Ship Check again. ## Not proven (1) ### #1 Tests fail: `npm run test` **BLOCKER** Found by: prove-it-works FAIL · cross-checks CC5 - **Finding:** `npm run test` exited with code 1. - **Evidence:** - `$ npm run test (exit 1, 0.34s)` … - ` error: |-` - ` code: 'ERR_ASSERTION'` - ` name: 'AssertionError'` - ` expected: 'hello-world'` - **Impact:** What ships is not proven to work; a failing test usually means a broken feature or a failed deploy. - **Fix:** 1. Fix the code until `npm run test` passes, then run Ship Check again. Do not skip, loosen or delete tests to get to green. … ## Skills run | Skill | Folder used | Status | Its verdict | Time | | --- | --- | --- | --- | --- | | broke-after-deploy | broke-after-deploy (schema 2) | ok | No known deploy breakers found. | 0.3s | | key-leaked | key-leaked (schema 2) | ok | No exposed secrets found | 0.4s | | bill-spiked | bill-spiked (schema 2) | ok | No uncapped spend found in the code. | 0.5s | | prove-it-works | prove-it-works (schema 2) | ok | NOT DONE: 1 failing check, 0 problems in the change, no change to inspect | 0.8s | Checks run by prove-it-works: `npm run test` FAIL Notes from the skills (information, not gaps): - broke-after-deploy: Host not detected, so host-specific problems (serverless file writes, SQLite, ports) are reported as 'check' rather than 'will break'. Pass --target with your host for a definite answer. …
What the skill tells Claude to do next: copy the first line, then open the failing test and the code it tests. slugify("Hello, World!") returns hello,-world!, so the test is right and the code is wrong. The checklist says to fix the code, not the test, and never to skip, loosen or delete a test to get to green.
It also reads the note from Broke After Deploy: no host was detected, so host-specific problems would be reported as "check" rather than "will break". Claude asks where you deploy, or reruns with --target <host> for a definite answer, instead of promoting anything on a guess.
With the fix below applied to a copy, the test passed and the first line became SAFE TO SHIP.
function slugify(title) {
- return title.toLowerCase().replace(/ /g, "-");
+ return title
+ .toLowerCase()
+ .replace(/[^a-z0-9\s-]/g, "")
+ .trim()
+ .replace(/\s+/g, "-");
}
3. A clean shop: UNVERIFIED first, SAFE TO SHIP only after the tests run
An Express shop on Render with one AI feature done right: the route needs a signed-in user, has a rate limit, and caps output at 150 tokens. It also has two look-alikes that should not be flagged: a Stripe publishable key in browser code (public by design) and the OpenAI key in a gitignored .env that was never committed and is read only by server code.
// src/server.js
const limiter = rateLimit({ windowMs: 60 * 1000, max: 10 });
app.post("/api/describe", requireUser, limiter, describe);
// src/describe.js
model: "gpt-4o-mini",
max_tokens: 150,
// public/checkout.js
// Stripe publishable key: public by design.
const stripe = Stripe("pk_live_TCtk…NMg7");
**UNVERIFIED: tests and build not run (--no-tests); nothing will break, leak or cost you in what was checked** Do these first: 1. Set the provider spending cap, then add sign-in, rate limits and output limits. Repo: clean-shop · 3 of 4 skills ran · 0.5s · history: 1 commit scanned · commit 6e1aeb0d46 … ## Not verified - tests and build not run (--no-tests) … ## Skills run | Skill | Folder used | Status | Its verdict | Time | | --- | --- | --- | --- | --- | | broke-after-deploy | broke-after-deploy (schema 2) | ok | No known deploy breakers found. | 0.3s | | key-leaked | key-leaked (schema 2) | ok | No exposed secrets found | 0.4s | | bill-spiked | bill-spiked (schema 2) | ok | No uncapped spend found in the code. | 0.5s | | prove-it-works | prove-it-works | skipped | | - | …
**SAFE TO SHIP** Do these first: 1. Set the provider spending cap, then add sign-in, rate limits and output limits. Repo: clean-shop · 4 of 4 skills ran · 0.9s · history: 1 commit scanned · commit 6e1aeb0d46 … ## Skills run | Skill | Folder used | Status | Its verdict | Time | | --- | --- | --- | --- | --- | | broke-after-deploy | broke-after-deploy (schema 2) | ok | No known deploy breakers found. | 0.4s | | key-leaked | key-leaked (schema 2) | ok | No exposed secrets found | 0.6s | | bill-spiked | bill-spiked (schema 2) | ok | No uncapped spend found in the code. | 0.6s | | prove-it-works | prove-it-works (schema 2) | ok | CHECKS PASS (not fully verified): all 1 check passed, no problems in the change; there is no change to inspect | 0.9s | Checks run by prove-it-works: `npm run test` PASS …
What the skill tells Claude to do next: with --no-tests, read the gap aloud and never call it safe. Nothing will break, leak or cost you in what was checked, but nothing proves the code works. The way to close the gap is to run the tests, which is what the second run did.
No look-alike was flagged. Key Leaked saw the Stripe key and rated it publishable, so it does not count as a leak. Bill Spiked found the paid call but also the sign-in, the rate limit and the output cap, so nothing goes under "Will cost you".
Even at SAFE TO SHIP, the checklist has Claude do two more things: point you to the spending caps (OpenAI has a hard monthly limit; Render's cap is partial), and read whether the tests cover what you are shipping. Here the only test covers price formatting, not the /api/describe route, so Claude says so in one line.
- uses: actions/checkout@v4
with:
fetch-depth: 0
+ - run: npm ci
- - run: python3 .claude/skills/ship-check/scripts/ship_check.py . --ci --no-tests
+ - run: python3 .claude/skills/ship-check/scripts/ship_check.py . --ci --fail-on leak,failing
Results
We tested Ship Check on 4 new held-out projects (Oct 14), graded blind, against plain Claude with no skill.
| Claude with Ship Check | Plain Claude | |
|---|---|---|
| Verdicts right | 4 of 4 | 4 of 4 |
| Planted problems found | all 20 | all 20 |
| Compound risks named | 7 of 7 | 4 of 7 |
| False items raised | 1 | 4 |
Both found every planted problem and got every verdict right. The difference is in the links between findings: Ship Check named all 7 compound risks, and raised fewer false items. It never says SAFE TO SHIP when a check did not run; it says UNVERIFIED.
Install Ship Check
Ship Check runs the other four skills, so all five folders have to sit side by side in the same skills folder. The bundle zip contains all five. If you already installed some of them, unzipping the bundle over them updates them.
Download ship-check-bundle.zip Free during launch. Checksums
python3 already; on Windows install it from python.org. Git is needed for the history checks.Claude Code
- Download
ship-check-bundle.zip(button above). - Unzip it into your personal skills folder so it works in every project:
Or into one project only (commit it to share it with your team):unzip ~/Downloads/ship-check-bundle.zip -d ~/.claude/skills/unzip ~/Downloads/ship-check-bundle.zip -d .claude/skills/ - Check it landed:
ls ~/.claude/skills/ - Use it. Claude Code picks up new skills without a restart (if
~/.claude/skillsdid not exist when your session started, run/reload-skills). Ask in plain words, for example “is this safe to ship?” right before you deploy, or type/ship-check.
Claude.ai (web and desktop app)
- Open Settings > Capabilities and make sure Code execution and file creation is on. Skills need it. (Available on Pro, Max, Team and Enterprise plans; on Enterprise an Owner enables Skills first.)
- In the Skills section, click Upload skill and upload each of the five zips, one at a time: broke-after-deploy.zip, key-leaked.zip, bill-spiked.zip, prove-it-works.zip, ship-check.zip. Do not unzip them.
- Check that it appears in your Skills list and switch it on.
- Start a chat, attach your project as a zip (or paste the error or alert) and ask, for example “is this safe to ship?”. In Claude.ai the scripts run in Claude’s sandbox on the files you upload, not on your computer.
We have tested Ship Check in Claude Code. In Claude.ai each skill is uploaded separately, and we have not yet confirmed that Ship Check finds the other four there.
Cursor
- Unzip into Cursor’s skills folder for all projects:
or into a project:unzip ~/Downloads/ship-check-bundle.zip -d ~/.cursor/skills/.cursor/skills/. Cursor also reads~/.claude/skills/, so a Claude Code install works in Cursor too. - In Agent chat, type
/and pick ship-check, or just ask: “is this safe to ship?”.
OpenAI Codex
- Unzip into your user skills folder:
or intounzip ~/Downloads/ship-check-bundle.zip -d ~/.agents/skills/.agents/skills/at the root of a repo. - Mention it with
$ship-check(or pick it from/skills), or just ask: “is this safe to ship?”.
Other agents, updates and removal
- Any agent that reads
SKILL.mdfiles: unzip into its skills folder. Each skill is a folder withSKILL.md,scripts/andreferences/. - Update: download the new zip and unzip it over the old one with
unzip -o. - Remove: delete the five skill folders from the skills folder.
- Check the download:
shasum -a 256 ship-check-bundle.zipand compare with SHA256SUMS.txt.
FAQ
Does it change my code?
Not without your OK. The scans are read-only. Fixes come as one diff at a time and are applied only with your approval, and it never installs anything. Two things can write: your project's own test, type check and build commands may write their usual output (skip them with --no-tests, which is strictly read-only), and with --ledger it appends one line per run to .techfix/ledger.md (date, verdict, counts, commit; never a secret) with its own .gitignore. The test-result cache lives outside your repo.
Does my code leave my machine?
The scripts don't send it anywhere. They use only Python's standard library, work offline, and never log in to your providers or read your dashboards. Claude's review pass reads your files the same way your agent already does in any session. In Claude.ai, the scripts run in Claude's sandbox on the files you upload.
Which stacks does it work with?
The deploy checks cover JavaScript and TypeScript apps (Next.js, Vite, React, Node, Express and others) and give basic checks for Python web apps (Django, FastAPI, Flask). For other stacks the deploy area is reported as not covered, so the verdict is UNVERIFIED at best and Claude reads the deploy config itself. Tests and builds run through your project's own commands (package.json scripts, Makefile, pytest, cargo, go and more).
How long does it take?
The scripts take about 1 second on these examples (0.4 to 1.1 seconds; about half a second on a rerun, because the test result is cached by a digest of your files). A real test suite and build take as long as they take, bounded by --test-timeout (300 seconds per command by default). Claude's review adds time.
How is this different from asking my agent "is this safe to ship?"
The same scans run every time, so nothing depends on what the agent remembers to look at. The cross-checks connect findings across skills, which is where plain Claude fell short in our test (4 of 7 compound risks, against 7 of 7). And the verdict is honest about gaps: if tests did not run or history was shallow, it says UNVERIFIED instead of safe.
What does it cost?
Ship Check is a bundle of all five skills, normally $19 once. It is free during launch.