Hmbown/CodeWhale · error · Error
refusing to run with a secret under CI
Error message
refusing to run with a secret under CI (${marker} is set); publish from the founder's machine What it means
refuseUnderCi scans CI_MARKERS environment variables and throws if any is set to a truthy value (0/false/no/off are allowed as opt-outs). The publish script must only run from the founder's machine where the signing secret lives, never inside CI where secrets and signing keys should not exist.
Solutions
- Run the publish script on the founder's machine with the CI markers unset (check `env | grep -i ci`)
- Explicitly unset the marker: `env -u CI -u GITHUB_ACTIONS node web/scripts/facts-publish.mjs ...`, or set it to an allowed falsy value like CI=0
- Do not bypass via CI=0 unless you truly are on the trusted local machine — the guard exists to keep the signing secret out of CI
Example fix
// before (CI) node web/scripts/facts-publish.mjs // after (local, markers cleared) env -u CI -u GITHUB_ACTIONS node web/scripts/facts-publish.mjs
Defensive patterns
Strategy: try-catch
Validate before calling
const ciMarkers = ["CI", "GITHUB_ACTIONS", "TEAMCITY_VERSION", "BUILD_NUMBER"];
const active = ciMarkers.filter((m) => process.env[m] && !/^(0|false|no|off)$/i.test(process.env[m]));
if (active.length) throw new Error(`publishing blocked under CI markers: ${active.join(", ")} — run from the founder's machine`); Try / catch
try {
await publishFacts(envelope);
} catch (err) {
if (err.message.includes("refusing to run with a secret under CI")) {
console.error("Unset the CI marker named in the message and run the publish from the trusted local machine");
process.exit(1);
}
throw err;
} Prevention
- Run publication only from the trusted local machine; never automate it in CI
- In shared shells/devcontainers, check `env | grep -i '^CI=' ` and unset inherited markers before publishing
- Keep the signing secret off CI runners entirely so CI publishing is impossible by construction
When it happens
Trigger: Running facts-publish.mjs (or any code path that calls postgrest, which calls refuseUnderCi) while CI env vars like CI, GITHUB_ACTIONS, or TEAMCITY_VERSION are set to non-falsy values, e.g. in a GitHub Actions workflow, container with CI=true inherited, or a local shell that exported CI=1.
Common situations: A developer tried to automate publishing in CI; a local environment had CI=true exported globally from previous tooling; running inside a devcontainer or test runner that sets CI.
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
- no trusted executable search path remains outside the…
- A full Git history is required to establish the PR merge…
- account_agent_model_unconfigured
- active plugin registry is missing its pre-dotenv…
- agent action=claim widens an enforced write scope, and the…
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/003707f992a688f8.
Report an issue: GitHub.
Appendix: source
Thrown at web/scripts/facts-publish.mjs:416
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, "''")}'`;
}
export function emitSql(envelope, { publishedBy = "", publicKeyB64, notes = "" }) {
if (!publicKeyB64) throw new Error("public key required to emit the facts_key row");
const check = verifyEnvelope(envelope, publicKeyB64);
if (!check.ok) throw new Error(`envelope does not verify: ${check.errors.join("; ")}`);
const payloadJson = Buffer.from(envelope.payload_b64, "base64").toString("utf8");
return [
"begin;",
`insert into public.facts_key (key_id, scope, algorithm, public_key, status)`,
` values (${sqlLiteral(envelope.key_id)}, 'global', 'ed25519', ${sqlLiteral(publicKeyB64)}, 'active')`,View on GitHub (pinned to 433685b202)