JuliusBrussee/caveman · error
--pubkey JSON is malformed
Error message
--pubkey JSON is malformed
What it means
A --pubkey file whose first character is '{' is parsed as JSON; if JSON.parse throws, the CLI reports "--pubkey JSON is malformed". Common JSON syntax faults: trailing commas, comments, single-quoted strings, or two objects concatenated.
Source
Thrown at packages/cli/src/index.ts:17433
if (bundle.public_keys !== undefined && !Array.isArray(bundle.public_keys)) throw new Error("public_keys must be an array");
if (bundle.schema === RECEIPT_BUNDLE_V2 && (!Array.isArray(bundle.public_keys) || bundle.public_keys.length === 0)) throw new Error("v2 bundle requires public_keys");
const keys = decodeUniqueKeyring(bundle.public_keys ?? [], "public_keys");
const currentInRing = keys.get(current.info.key_id);
if (currentInRing && !currentInRing.raw.equals(current.raw)) throw new Error(`public_key conflicts with public_keys entry ${current.info.key_id}`);
if (bundle.schema === RECEIPT_BUNDLE_V2 && !currentInRing) throw new Error("v2 public_keys must include public_key");
if (!currentInRing) keys.set(current.info.key_id, current);
return { current, keys };
}
async function pinnedReceiptKeys(file: string, current: DecodedReceiptKey): Promise<{ keys: Map<string, DecodedReceiptKey>; trust: string }> {
const source = (await readFile(file, "utf8")).trim();
if (!source.startsWith("{")) {
const pinned = decodeReceiptKey({ ...current.info, key: source }, "--pubkey");
if (!pinned.raw.equals(current.raw)) throw new Error("bundle public key does not match the published --pubkey");
return { keys: new Map([[current.info.key_id, pinned]]), trust: "pinned_public_key" };
}
let parsed: { public_key?: ReceiptPublicKey; public_keys?: ReceiptPublicKey[] };
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>]`);View on GitHub (pinned to 5184b3d11a)
Solutions
- Validate the file first: jq empty pubkey.json (or node -e 'JSON.parse(require("fs").readFileSync("pubkey.json","utf8"))')
- Fix the syntax fault the parser reports
- Ensure the file contains exactly one JSON object
Example fix
// before
{ "public_key": { "key_id": "k1", "alg": "Ed25519", "key": "AAA...", }, }
// after
{ "public_key": { "key_id": "k1", "alg": "Ed25519", "key": "AAA..." } } Defensive patterns
Strategy: validation
Validate before calling
const src = await readFile(pubkeyFile, "utf8");
if (src.trim().startsWith("{")) {
try { JSON.parse(src); } catch (e) {
throw new Error(`--pubkey file is not valid JSON: ${(e as Error).message}`);
}
} Type guard
function parsesAsJsonObject(s: string): boolean {
try { return typeof JSON.parse(s) === "object" && JSON.parse(s) !== null; } catch { return false; }
} Try / catch
try { execSync(`caveman receipts verify ${bundle} --pubkey ${pubkey}`); }
catch (e) {
if (/--pubkey JSON is malformed/.test(String((e as Error).message))) {
fail(`fix pubkey JSON syntax (jq empty ${pubkey} shows the error)`);
}
throw e;
} Prevention
- Generate key files programmatically (JSON.stringify), never by hand
- Run `jq empty` on key files in CI preflight
- Keep exactly one JSON object per file
When it happens
Trigger: Passing a --pubkey file that starts with '{' but is not valid JSON — e.g. hand-assembled key files, JSON with trailing commas, or a log line prefixed before the object.
Common situations: Hand-editing a keyring and leaving a trailing comma; concatenating two JSON documents; copying pretty-printed JSON with smart quotes from a chat/doc.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- --pubkey JSON must contain public_key or public_keys
- cave_live_eval_sandbox_profile_invalid
- caveman build: invalid .caveman/provider.json
- caveman agent: value is not canonically serializable
- caveman agent: invalid .caveman/provider.json
AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-08-18).
Data as JSON: /api/errors/f72643b87da947e9.
Report an issue: GitHub.