Hmbown/CodeWhale · error · Error
primary signing key is not pinned and active; refusing…
Error message
primary signing key is not pinned and active; refusing publication
What it means
activePublishingKey looks up the envelope's key_id among the repo-pinned TRUSTED_KEYS and requires that key to exist with status "active" before verifying the signature. If no pinned active key matches the envelope's key_id, publication is refused to prevent signing facts with an unpinned, revoked, or retired key.
Solutions
- Add the envelope's key_id and its base64 public key to TRUSTED_KEYS in web/lib/cloud-facts/keys.ts with status "active" (commit and land that change first)
- If the key was intentionally rotated, regenerate the envelope with the currently active key instead of resurrecting the revoked one
- Confirm the keys argument passed to activePublishingKey is the map built by loadTrustedKeysFromRepo, not an empty or stale list
Example fix
// before (keys.ts)
{ keyId: "k1", publicKey: "abc", status: "revoked" }
// after (new key pinned)
{ keyId: "k2", publicKey: "def", status: "active" } Defensive patterns
Strategy: validation
Validate before calling
import { loadTrustedKeysFromRepo } from "./facts-publish.mjs";
const keys = await loadTrustedKeysFromRepo();
if (!keys.has(envelope.key_id)) throw new Error(`key ${envelope.key_id} is not pinned in keys.ts — pin it before signing/publishing`);
if (keys.get(envelope.key_id).status !== "active") throw new Error(`key ${envelope.key_id} is ${keys.get(envelope.key_id).status}, not active`); Try / catch
try {
await activePublishingKey(envelope, keys);
} catch (err) {
if (err.message.includes("not pinned and active")) {
console.error(`Key ${envelope.key_id} is missing from TRUSTED_KEYS or not active — pin it or re-sign with the active key`);
process.exit(1);
}
throw err;
} Prevention
- Pin new keys in lib/cloud-facts/keys.ts (status "active") before signing envelopes with them
- After a rotation, regenerate envelopes with the new active key instead of reusing old ones
- Confirm the signing key_id equals a pinned active key_id before invoking the publish script
When it happens
Trigger: Calling activePublishingKey with an envelope whose key_id is absent from the keys list, matches a key whose status is not "active" (e.g. "revoked"/"retired"), or when the keys argument itself is empty or unvalidated.
Common situations: The signer generated a new Ed25519 key but never added it to lib/cloud-facts/keys.ts; the key was rotated and the old one marked revoked while the local build still signs with it; the wrong keys file/map was passed in.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- agent action=claim widens an enforced write scope, and the…
- allowlisted read-only executable
- append_allow_rules only accepts action = "allow"
- audited skill path does not match owned package
- audited skill root identity changed; refusing mutation
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/0e8ca0eb8c0120cf.
Report an issue: GitHub.
Appendix: source
Thrown at web/scripts/facts-publish.mjs:405
const table = tables[0];
const body = table[1].replace(/^\s*\/\/.*$/gm, "");
const keys = [];
const remainder = body.replace(/\{\s*keyId:\s*"([^"]+)",\s*publicKey:\s*"([^"]+)",\s*status:\s*"([^"]+)"\s*,?\s*\}/g, (_, keyId, publicKey, status) => {
keys.push({ keyId, publicKey, status });
return "";
});
if (remainder.replace(/[\s,]/g, "")) throw new Error("unparsed TypeScript TRUSTED_KEYS entry");
return validateTrustedKeys(keys);
}
function loadTrustedKeysFromRepo() {
const keys = parseTsKeys(readBoundedFile(resolve(WEB_ROOT, "lib/cloud-facts/keys.ts"), 64 * 1024).toString("utf8"));
return new Map(keys.map((key) => [key.keyId, key]));
}
export function activePublishingKey(envelope, keys, now = Date.now()) {
const key = validateTrustedKeys(keys).find((key) => key.keyId === envelope.key_id && key.status === "active");
if (!key) throw new Error("primary signing key is not pinned and active; refusing publication");
const check = verifyEnvelope(envelope, key.publicKey);
if (!check.ok) throw new Error(`envelope does not verify: ${check.errors.join("; ")}`);
if (!Number.isFinite(now) || utcTime(check.payload.published_at) > now + 300_000 ||
(check.payload.not_after != null && utcTime(check.payload.not_after) <= now)) throw new Error("publication timestamp is future or expired");
return { key, check };
}
function refuseUnderCi() {
for (const marker of CI_MARKERS) {
if (process.env[marker] && !/^(0|false|no|off)$/i.test(process.env[marker])) {
throw new Error(`refusing to run with a secret under CI (${marker} is set); publish from the founder's machine`);
}
}
}
function sqlLiteral(value) {
if (value === null || value === undefined) return "null";
return `'${String(value).replace(/'/g, "''")}'`;View on GitHub (pinned to 433685b202)