Hmbown/CodeWhale · error · Error
set CODEWHALE_FACTS_SIGNING_KEY (PEM) or…
Error message
set CODEWHALE_FACTS_SIGNING_KEY (PEM) or CODEWHALE_FACTS_SIGNING_KEY_FILE
What it means
loadPrivateKeyFromEnv resolves the signing key from CODEWHALE_FACTS_SIGNING_KEY (PEM text) or CODEWHALE_FACTS_SIGNING_KEY_FILE (path). If neither variable yields a non-empty PEM string, this error is thrown before any signing can happen. It is a configuration-missing error, not a key-format error.
Solutions
- Export CODEWHALE_FACTS_SIGNING_KEY with the PEM contents: export CODEWHALE_FACTS_SIGNING_KEY="$(cat key.pem)"
- Or set CODEWHALE_FACTS_SIGNING_KEY_FILE to a regular single-link file containing one PEM key
- Verify with `printenv CODEWHALE_FACTS_SIGNING_KEY | head -1` that it is non-empty
- If running under CI, this is intentional — sign locally and publish the artifact instead
Example fix
// before node web/scripts/facts-publish.mjs publish payload.json // after export CODEWHALE_FACTS_SIGNING_KEY="$(cat ~/.codewhale/ed25519.pem)" node web/scripts/facts-publish.mjs publish payload.json
Defensive patterns
Strategy: validation
Validate before calling
const pem = process.env.CODEWHALE_FACTS_SIGNING_KEY;
const file = process.env.CODEWHALE_FACTS_SIGNING_KEY_FILE;
if (!pem && !file) throw new Error('set CODEWHALE_FACTS_SIGNING_KEY (PEM) or CODEWHALE_FACTS_SIGNING_KEY_FILE'); Type guard
const hasSigningKeyEnv = () => Boolean(process.env.CODEWHALE_FACTS_SIGNING_KEY || process.env.CODEWHALE_FACTS_SIGNING_KEY_FILE);
Try / catch
try { key = loadPrivateKeyFromEnv(); } catch (e) { if (e.message.startsWith('set CODEWHALE_FACTS_SIGNING_KEY')) { console.error('Export CODEWHALE_FACTS_SIGNING_KEY or CODEWHALE_FACTS_SIGNING_KEY_FILE and retry'); process.exit(2); } throw e; } Prevention
- Export signing env vars in the same shell/profile that runs the publish script
- Use direnv or CI secrets to keep the variables present per-environment
- Remember this script refuses under CI — sign locally by design
- Verify non-emptiness: empty-string env values count as missing
When it happens
Trigger: Calling buildEnvelope/publish paths without CODEWHALE_FACTS_SIGNING_KEY set, or with CODEWHALE_FACTS_SIGNING_KEY_FILE set to an empty/unreadable-by-this-path file, or both unset; also when the env var is set but empty ('').
Common situations: Running the publish script in a fresh shell/container where the env vars were never exported; CI job where refuseUnderCi deliberately forbids keys; exporting the variable in one terminal but running the script in another.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- signing key exceeds size limit
- signing key must be Ed25519
- 127
- A pinned task provider requires an explicit model
- agent profile reasoning_effort must be one of: inherit…
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/923402f904e46c84.
Report an issue: GitHub.
Appendix: source
Thrown at web/scripts/facts-publish.mjs:366
if (!stat.isFile() || stat.nlink !== 1 || stat.size > maxBytes || stat.ino !== before.ino || stat.dev !== before.dev) throw new Error("file is not a bounded regular single-link file");
const bytes = Buffer.alloc(maxBytes + 1);
let size = 0;
while (size <= maxBytes) {
const count = readSync(fd, bytes, size, maxBytes + 1 - size, null);
if (!count) break;
size += count;
}
if (size > maxBytes) throw new Error("file exceeds size limit");
return bytes.subarray(0, size);
} finally { closeSync(fd); }
}
function loadPrivateKeyFromEnv() {
refuseUnderCi();
let pem = process.env.CODEWHALE_FACTS_SIGNING_KEY;
const file = process.env.CODEWHALE_FACTS_SIGNING_KEY_FILE;
if (!pem && file) pem = readBoundedFile(file, 16 * 1024).toString("utf8");
if (!pem) throw new Error("set CODEWHALE_FACTS_SIGNING_KEY (PEM) or CODEWHALE_FACTS_SIGNING_KEY_FILE");
if (Buffer.byteLength(pem) > 16 * 1024) throw new Error("signing key exceeds size limit");
const key = createPrivateKey({ key: pem, format: "pem" });
if (key.asymmetricKeyType !== "ed25519") throw new Error("signing key must be Ed25519");
return key;
}
export function validateTrustedKeys(keys) {
const seen = new Set();
for (const key of keys) {
if (!KEY_ID_RE.test(key.keyId) || seen.has(key.keyId) || !["active", "retired"].includes(key.status) || strictBase64(key.publicKey, 32).length !== 32) throw new Error("invalid or duplicated pinned key");
seen.add(key.keyId);
}
return keys;
}
/** Deliberately narrow syntax: a changed/unparseable table must fail the gate. */
export function parseTsKeys(text) {
const source = text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");View on GitHub (pinned to 433685b202)