Troubleshooting

Shipmoor Team
July 31, 2026
7 min read

Not yet generally available. Blast Radius ships in Shipmoor CLI 0.9.0, which is still in the release process. This page describes shipped behavior on the release candidate; the command is not on the stable channel yet.

Blast Radius discloses rather than guesses, so most surprises arrive as a named reason rather than as silence. This page is that vocabulary, and what to do about each.

Not-checkable reasons

These appear in the not-checkable bucket. The entry keeps its committed place, is never quietly pinned, and never affects the exit code.

missing acceptance file

What it means. The repo has no frozen acceptance set at .shipmoor/acceptance.json, so no obligation has a content identity to compare against. Every realizes link reads this.

Fix it.

shipmoor claim-check freeze .
git add .shipmoor/acceptance.json && git commit -m "freeze the acceptance set"

Authoring .shipmoor/acceptance.yaml is not enough on its own. The YAML is the contract you write; the frozen JSON pin is what gives each obligation a stable identity, and it is what proves the set you run is the set you committed. Commit both.

unknown item id

What it means. A link’s item is not in the frozen acceptance set.

Usual causes. A typo in the id. An obligation that was renamed or removed. A link authored against a set that has since been re-frozen with different ids.

Fix it. Correct the id in .shipmoor/links.yaml, or remove the link if the obligation is genuinely gone. The next run pins it.

absent path

What it means. A code path in the link is not in the repo.

Usual causes. The file moved or was renamed. A typo. A path written relative to the wrong root — paths are repo-relative.

Fix it. Update the path. Note that a moved file is a genuine editorial decision, not a mechanical one: the link is a claim about which code realizes an obligation, and only you know whether the moved file is still that code.

The graph looks empty

A repo with no store

$ shipmoor blast

One honest line naming where links come from, and exit 0. This is correct behavior, not a failure.

Blast never scaffolds the store, because an engine-created store would be an approval nobody made. Author your first link by hand (Quickstart) or run Claim Check and let graduating obligations mint links for you.

A store exists but nothing classifies

Check, in order:

  1. Is .shipmoor/acceptance.json present and committed? Without it, every realizes link reads missing acceptance file. This is by far the most common cause.
  2. Do the item ids match the frozen set? Look for unknown item id in the not-checkable bucket.
  3. Do the code paths exist? Look for absent path.
  4. Is the change surface what you think it is? The default is the working-tree diff. If you have committed your change, use --diff main...HEAD or --from/--to, or --all to read the whole tree regardless of the diff.

No verifies edges at all

The verifies join reads claim_check.runners.test, because the rung it calls binds only a check something can run. Without a runner configured, no edges are derived — and the run says so rather than presenting an empty graph as a clean one.

claim_check:
  runners:
    test: "pytest {ref}"

If a runner is configured and edges still do not appear, the join is an exact string match on the obligation’s expected check tag (ac_01_7c9d21aa) in the test id or file name. A check whose name merely reads like the obligation earns no edge — that is the law, not a bug. Tag the check.

A comment-only edit made it suspect

Working as designed. Identity is content, not meaning. A comment-only or whitespace-only edit changes the bytes and therefore the identity.

The moment a comparator starts deciding which edits “really” changed the promise, it is making a semantic judgment, and there is no honest way to explain to a reader why one edit counted and another did not.

The relief valve is an explicit re-pin, which records your judgment in the committed diff:

shipmoor blast --repin ac-01.realizes.login

A verifies edge says the check is stale, but it just ran

Working as designed, and the line is carrying two clauses, not one.

When a change edits an obligation’s statement, the whole acceptance set is re-frozen and every expected tag moves with it. A check tagged against the previous statement is still shown, annotated as pinned against that statement.

Freshness and that annotation are independent axes. A check can have run for this change (fresh) and still be tagged against a statement the change superseded, because running a check does not re-tag it. Re-tagging the check resolves the annotation.

--repin did not do what I expected

BehaviorWhy
Stated no-opThe link is not currently suspect. In sync and silent links have nothing to accept.
RefusedThe link’s endpoint was deleted. A deleted endpoint has no identity to pin, and recording a hollow success would be a lie in the store.
Usage errorThe link id does not exist in the store.
Nothing written--no-write was set. The intended change is disclosed; the file on disk is untouched.

Usage errors (exit 2)

Malformed committed store

An unreadable store is a usage error, never an empty graph. An empty graph looks identical to a clean repo, so a parse failure that degraded to one would report “nothing to worry about” on a repo whose governance file is broken.

Check the YAML, and check for a verifies entry — a stored verifies kind is a loader error, not a tolerated extension, because those edges are derived every run and a stored one could outlive the check it names.

Malformed config

An unparseable config is a usage error, never a silent fall back to the default. So is an unrecognised suspect_threshold value.

If suspect_threshold: fial silently became warn, a team would believe they had a ratchet, CI would pass, and nobody would find out until a drifted obligation shipped.

Note the boolean spellings: off, no, n, and false in any capitalisation are all read as off. The true spellings (on, yes, true) are not thresholds and are rejected. See Configuration.

Conflicting selectors

--staged, --diff, --all, and the --from/--to pair are mutually exclusive. --from and --to are given together.

The command will not run at all

shipmoor blast is gated on the blast_radius entitlement. A caller without an active IC subscription gets upgrade guidance and the command does not run.

shipmoor capabilities

That lists what your license actually grants. If you have signed in and blast_radius is still absent, see Plans & tiers.

The report keeps showing up as a changed file

Ignore it — the report is generated, not authored:

.shipmoor/blast.v1.json

Keep the entry targeted. A broad .shipmoor/ entry would also ignore .shipmoor/links.yaml, and an ignored link store breaks the approval mechanic the whole feature rests on: committing the store is what approves it.

Leaving it untracked is harmless in the meantime. The report is classed as a Shipmoor control artifact and never counts as ungoverned code, and the count settles after one run rather than growing.

Last updated on July 31, 2026

Was this article helpful?

Your response is saved on this device.

Related Articles