JuliusBrussee/caveman · error
usage: ${invokedCommand("receipts")} verify <bundle.json> [-
Error message
usage: ${invokedCommand("receipts")} verify <bundle.json> [--pubkey <file>] What it means
receiptsVerify needs a positional bundle path; positionalAfterOptions scans argv after the subcommand, skipping only --pubkey and its value. If no positional remains, the CLI prints the usage line and exits non-zero.
Source
Thrown at packages/cli/src/index.ts:17451
try { parsed = JSON.parse(source); } catch { throw new Error("--pubkey JSON is malformed"); }
const infos = Array.isArray(parsed.public_keys) ? parsed.public_keys : parsed.public_key ? [parsed.public_key] : [];
if (infos.length === 0) throw new Error("--pubkey JSON must contain public_key or public_keys");
const keys = decodeUniqueKeyring(infos, "--pubkey public_keys");
const pinnedCurrent = keys.get(current.info.key_id);
if (!pinnedCurrent || !pinnedCurrent.raw.equals(current.raw)) throw new Error("trusted --pubkey keyring does not contain the bundle public key");
return { keys, trust: "pinned_keyring" };
}
// receiptsVerify validates a signed receipt bundle offline (no network). A raw
// --pubkey pins the current key; JSON may independently pin a full rotation
// keyring. Without either, embedded keys prove self-consistency, not publisher
// authenticity. Exits non-zero on any included content, signature, or
// scope-chain break. Tail/scope omission needs separately trusted head manifest;
// bundle output states completeness is not attested.
// caveman receipts verify <bundle.json> [--pubkey <file>]
async function receiptsVerify(argv: string[]) {
const file = positionalAfterOptions(argv.slice(1), new Set(["--pubkey"]));
if (!file) throw new Error(`usage: ${invokedCommand("receipts")} verify <bundle.json> [--pubkey <file>]`);
let bundle: ReceiptBundle;
try { bundle = JSON.parse(await readFile(file, "utf8")) as ReceiptBundle; } catch (e) { return fail(`invalid bundle JSON: ${(e as Error).message}`); }
if (!Array.isArray(bundle.receipts)) return fail("bundle receipts must be an array");
try {
const embedded = embeddedReceiptKeys(bundle);
const pubkeyFile = flagFrom(argv, "--pubkey", "");
const pinned = pubkeyFile ? await pinnedReceiptKeys(pubkeyFile, embedded.current) : null;
const keys = pinned?.keys ?? embedded.keys;
for (const receipt of bundle.receipts) {
const embeddedKey = embedded.keys.get(receipt.signature?.key_id);
if (!embeddedKey) return fail(`seq ${receipt.seq}: no embedded public key for key_id ${String(receipt.signature?.key_id)}`);
if (pinned) {
const trusted = keys.get(receipt.signature?.key_id);
if (!trusted) return fail(`seq ${receipt.seq}: key_id ${String(receipt.signature?.key_id)} is not present in trusted --pubkey material`);
if (!trusted.raw.equals(embeddedKey.raw)) return fail(`seq ${receipt.seq}: embedded key ${receipt.signature.key_id} does not match trusted --pubkey material`);
}
}View on GitHub (pinned to 5184b3d11a)
Solutions
- Add the bundle path: caveman receipts verify <bundle.json> [--pubkey <file>]
- In scripts, guard with [ -n "$BUNDLE" ] or set -u so unset variables fail loudly
- Put the bundle path before the flags to avoid it being swallowed as a flag value
Example fix
# before caveman receipts verify --pubkey keys.json # after caveman receipts verify bundle.json --pubkey keys.json
Defensive patterns
Strategy: validation
Validate before calling
const args = ["receipts", "verify", bundlePath];
if (pubkeyPath) args.push("--pubkey", pubkeyPath);
if (!bundlePath || !existsSync(bundlePath)) {
throw new Error(`bundle path missing or not found: ${bundlePath}`);
} Type guard
function hasBundlePositional(argv: string[]): boolean {
const rest = argv.slice(1);
for (let i = 0; i < rest.length; i++) {
if (rest[i] === "--pubkey") { i++; continue; }
return true;
}
return false;
} Try / catch
try { execFileSync("caveman", args); }
catch (e) {
if (/^usage: caveman receipts verify/.test(String((e as Error).message))) fail("missing bundle path argument");
throw e;
} Prevention
- Quote and check script variables: [ -n "$BUNDLE" ] || exit 1, and use set -u
- Put the bundle path before flags in the command line
- Wrap CLI calls in scripts that validate arguments first
When it happens
Trigger: Running `caveman receipts verify` with no file, or `caveman receipts verify --pubkey keys.json` where the bundle path was forgotten (or was consumed as the --pubkey value).
Common situations: Shell scripts that pass empty variables ("$BUNDLE" unset expands to nothing); flag-order mistakes; copy-pasting a command minus the path.
Understand the failure class
Background: "no subcommand specified" and "... is required": CLI errors when a required argument is missing — this error's family across 13 libraries.
Related errors
- --provider requires a value
- unknown option ${value}
- usage: npm create @caveman-ai/agent@latest <project> [--prov
- cave_stale_lock:${checked.stale.join(",")}: run npm run buil
- cave_nested_usage_incomplete
AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-08-18).
Data as JSON: /api/errors/90337d2aa9be018d.
Report an issue: GitHub.