JuliusBrussee/caveman · error
contains duplicate key_id
Error message
${label} contains duplicate key_id ${decoded.info.key_id} What it means
Thrown by decodeUniqueKeyring when two or more entries in a public_keys array share the same key_id. The keyring is keyed by key_id so receipts can select the right verifying key; duplicates would make that mapping ambiguous, so the verifier refuses the bundle.
Solutions
- Deduplicate the public_keys array so each key_id appears once.
- If the duplicate has different key material, assign the new key a distinct key_id.
- Regenerate the bundle with the official export command, which builds a unique keyring.
Example fix
// before public_keys: [k(\"2026-q3\", A), k(\"2026-q3\", B)] // after public_keys: [k(\"2026-q3\", A), k(\"2026-q4\", B)]
Defensive patterns
Strategy: validation
Validate before calling
const ids = bundle.public_keys.map(k => k.key_id);
if (new Set(ids).size !== ids.length) throw new Error('duplicate key_id in public_keys'); Type guard
function hasUniqueKeyIds(keys: { key_id: string }[]): boolean {
return new Set(keys.map(k => k.key_id)).size === keys.length;
} Try / catch
try {
verifyBundle(bundle);
} catch (e) {
if ((e as Error).message.includes('duplicate key_id')) console.error('deduplicate public_keys before verifying');
throw e;
} Prevention
- Deduplicate by key_id when merging bundles.
- Assign a fresh key_id on every key rotation instead of reusing one.
- Validate keyring uniqueness at bundle-build time.
When it happens
Trigger: Verifying a v2 receipt bundle whose public_keys array contains two entries with identical key_id values (with otherwise possibly different key material or a plain accidental repeat).
Common situations: Merging bundles by concatenating public_keys arrays; copying an entry to update it instead of replacing it; re-adding the same key twice during key rotation tooling.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- bundle completeness cannot be attested by unsigned export…
- duplicate provider/model/region row
- ed25519 public key must be 32 bytes, got
- envelope: object kind is required
- envelope: organization scope is required
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/ef10079b02a5037a.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:17860
prev = r;
}
return null;
}
function decodeReceiptKey(info: ReceiptPublicKey, label: string): DecodedReceiptKey {
if (!info || typeof info.key_id !== "string" || !info.key_id.trim()) throw new Error(`${label} key_id is required`);
if (info.alg !== "Ed25519") throw new Error(`${label} has unsupported algorithm ${String(info.alg)}`);
if (typeof info.key !== "string" || !info.key.trim()) throw new Error(`${label} key is required`);
const raw = Buffer.from(info.key, "base64");
if (raw.length !== 32 || raw.toString("base64") !== info.key) throw new Error(`${label} must be a canonical base64 Ed25519 public key`);
return { info, raw, key: ed25519PublicKey(raw) };
}
function decodeUniqueKeyring(infos: ReceiptPublicKey[], label: string): Map<string, DecodedReceiptKey> {
const keys = new Map<string, DecodedReceiptKey>();
for (const [index, info] of infos.entries()) {
const decoded = decodeReceiptKey(info, `${label}[${index}]`);
if (keys.has(decoded.info.key_id)) throw new Error(`${label} contains duplicate key_id ${decoded.info.key_id}`);
keys.set(decoded.info.key_id, decoded);
}
return keys;
}
function embeddedReceiptKeys(bundle: ReceiptBundle): { current: DecodedReceiptKey; keys: Map<string, DecodedReceiptKey> } {
if (bundle.schema !== RECEIPT_BUNDLE_V1 && bundle.schema !== RECEIPT_BUNDLE_V2) throw new Error(`unsupported bundle schema ${String(bundle.schema)}`);
if (bundle.verification_coverage !== undefined && bundle.verification_coverage !== INCLUDED_RECEIPTS_ONLY) throw new Error(`unsupported unsigned verification coverage ${String(bundle.verification_coverage)}`);
if (bundle.completeness_attested === true) throw new Error("bundle completeness cannot be attested by unsigned export metadata");
const current = decodeReceiptKey(bundle.public_key, "public_key");
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 };View on GitHub (pinned to 3ee70a1026)